{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "# CPSC 330 Lecture 14"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "### Lecture plan\n",
    "\n",
    "- 👋\n",
    "- **Turn on recording**\n",
    "- Announcements\n",
    "- Vectors, distances, neighbours (25 min)\n",
    "- Cosine similarity (15 min)\n",
    "- Break (5 min)\n",
    "- Ratings data (5 min)\n",
    "- Sparse matrices (15 min)\n",
    "- KNN for product similarity (5 min)\n",
    "- Distances with sparse data (15 min)\n",
    "\n",
    "**Note: this lecture is too long - can we cover sparse matrices earlier on?**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Learning objectives\n",
    "\n",
    "- Connect the ideas of features to vectors\n",
    "- Compare Euclidean distance vs. cosine similarity in general, and in the context of product similarity\n",
    "- Appropriately select between dense and sparse matrix data structures\n",
    "- Apply nearest neighbour algorithms for product recommendations"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "slideshow": {
     "slide_type": "skip"
    }
   },
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "from scipy.sparse import csr_matrix, csc_matrix\n",
    "\n",
    "import seaborn as sns\n",
    "\n",
    "from sklearn.neighbors import NearestNeighbors\n",
    "from sklearn.metrics.pairwise import cosine_similarity, cosine_distances, euclidean_distances\n",
    "\n",
    "from sklearn.model_selection import train_test_split\n",
    "from sklearn.preprocessing import StandardScaler, OrdinalEncoder, OneHotEncoder\n",
    "from sklearn.impute import SimpleImputer\n",
    "from sklearn.compose import ColumnTransformer, TransformedTargetRegressor\n",
    "from sklearn.pipeline import Pipeline, make_pipeline\n",
    "from sklearn.feature_extraction.text import CountVectorizer"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "from IPython.core.display import display, HTML"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.rcParams['font.size'] = 16"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Announcements\n",
    "\n",
    "(same as Tuesday)\n",
    "\n",
    "- Midterm grading in progress\n",
    "- hw6 will be posted ~~at the end of this week or~~ this weekend hopefully\n",
    "- Lecture 12 screencast posted, please watch before hw6\n",
    "- Final exam format posted: https://piazza.com/class/kb2e6nwu3uj23?cid=429"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Vectors, distances, neighbours (20 min)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Distances between points\n",
    "\n",
    "- First, we need to talk a bit about vectors and distances.\n",
    "- Let's return to to the cities dataset from the beginning of the course."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "cities_df = pd.read_csv('data/cities_USA.csv', index_col=0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "blue = cities_df.query('vote == \"blue\"')\n",
    "red  = cities_df.query('vote == \"red\"')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.scatter(blue[\"lon\"], blue[\"lat\"], color=\"blue\", alpha=0.3);\n",
    "plt.scatter(red[\"lon\"], red[\"lat\"], color=\"red\", alpha=0.3);\n",
    "plt.ylabel(\"latitude\");\n",
    "plt.xlabel(\"longitude\");"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's take 2 points:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "cities_X = cities_df[['lon', 'lat']]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "two_cities = cities_X.sample(2, random_state=30)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAYsAAAEVCAYAAAARjMm4AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/d3fzzAAAACXBIWXMAAAsTAAALEwEAmpwYAABRzUlEQVR4nO29eXic1Xmwfz/aNRprH8u2xhs2NnjBLBIOEOKwJZgADomTNmnLkgIN7Y987RdoSrqQr21Km7hp0rRpA0kLLaFpAnFMAph9C4uRMZttwPsytiRr33ed3x/PvJ7xeKTRyNr13Nc116t513NeSec5z3rEOYdhGIZhDEbKeDfAMAzDmPiYsDAMwzASYsLCMAzDSIgJC8MwDCMhJiwMwzCMhJiwMAzDMBIypsJCRF4QETfAZ3PUeQUi8iMRqRWRNhF5RkRWjmVbDcMwjAhpY/y8PwRyY/ZdAHwHeBRARCT880LgdqABuAt4XkTOds6Fxq65hmEYBoCMd1KeiPwY+F1gtnOuXkTWAb8ELnXOPR8+Jw/YDzzonPvKuDXWMAxjmjKuPgsRyQY+B/zKOVcf3n0tcNQTFADOuSbgV8C6sW+lYRiGMdZmqFg+A8wAHojatxzYHufcHcD1IuJ3zrUOdtPi4mK3YMGCEWukYRjGdODNN9+sdc4F4h0bb2FxPXAMeCJqXyFwIM65nuZRAJwkLETkVuBWgHnz5rF169YRbahhGMZUR0QODnRs3MxQIjIHuBz4iXOuN/oQEM+RIoPdzzl3r3OuzDlXFgjEFYyGYRjGMBlPn8Xvhp//QMz+elS7iKUgvG0YzUYZhmEYJzOewuJ64B3n3Dsx+3egfotYlgGHEvkrDMMwjJFnXISFiJShAiFWqwDNsSgVkTVR5+cC14SPGYZhGGPMeGkW1wO9wENxjj0KvAY8KCK/LSKfDO8T4Ftj10TDMAzDY8yjoUQkHfgCsNk5Vx173DnXLyJXAxuAHwBZqPC4xDl3eEwbOx0JhaCiAmpqIBCA8nIIBse7VYZhjDNjrlk453qccwHn3DWDnFPvnPuSc67QOedzzl0Wx7dhjDShEGzaBO3tUFKi202bdL9hGNOa8c6zmFxM9Vl3RQXk50NuuHyXt62omFr9NAwjaaxE+VCZDrPumhrw+0/c5/frfsMwpjUmLIZK9Kw7JUW3+fm6f6oQCEBrTGRya6vuNwxjWmPCYqhMh1l3eTk0NkJzM/T367axUfcbhjGtMWExVKbDrDsYhHXrwOeD6mrdrltn/grDMMzBPWTKy9VHAapRtLbqrHvNmkEvm3QEgyYcDMM4CdMshorNug3DmMaYZpEMNus2DGOaYpqFYRiGkRATFoZhGEZCTFgYhmEYCTFhYRiGYSTEhIVhGIaREBMWhmEYRkJMWBiGYRgJMWFhGIZhJMSEhWEYhpEQExaGYRhGQkxYGIZhGAkxYWEYhmEkxISFYRiGkRATFoZhGEZCTFgYhmEYCTFhYRiGYSTEhIVhGIaREBMWhmEYRkJMWBiGYRgJMWFhGIZhJMSEhWEYhpEQExaGYRhGQkxYGIZhGAkxYWEYhmEkxISFYRiGkRATFoZhGEZCxkVYiMhVIvKSiLSKSLOIbBWRS6OOF4jIj0SkVkTaROQZEVk5Hm01DMMwxkFYiMgfAJuAN4HrgM8BPwd84eMCPApcCdwOfBZIB54XkeBYt9cwDMOAtLF8mIgsAL4L3Omc+27UoSejfr4W+ChwqXPu+fB1rwH7gT8FvjIWbTUMwzAijLVm8SWgH/j3Qc65FjjqCQoA51wT8Ctg3eg2zzAMw4jHWAuLjwIfAL8tIntFpFdE9ojIH0WdsxzYHufaHcA8EfGPRUMNwzCMCGMtLOYApwPfBv4e+ATwNPAvIvJ/wucUAg1xrq0Pbwvi3VhEbg07yrfW1NSMbKsNwzCmOWMtLFKAGcAfOOfuc84955y7DdgM3BV2bgvg4lwrg93YOXevc67MOVcWCARGvOGGYRjTmbEWFnXh7dMx+58CSoDZqAZRGOdaT6OIp3UYhmEYo8hYC4sdA+z3tIb+8DnL45yzDDjknGsdjYYZhmEYAzPWwmJjePvJmP2fBELOuSo0x6JURNZ4B0UkF7gmfMwwDMMYY8Y0zwJ4HHge+KGIFAP7gPWoo/um8DmPAq8BD4rInajZ6S5U+/jWGLfXMAzDYIyFhXPOicingXuA/4f6IT4Afsc591D4nH4RuRrYAPwAyEKFxyXOucNj2V7DMAxDEefiBR5NbsrKytzWrVvHuxmGYRiTChF50zlXFu+YVZ01DMMwEmLCwjAMw0iICQvDMAwjISYsDMMwjISYsDAMwzASYsLCMAzDSIgJC8MwDCMhJiwMwzCMhJiwMAzDMBJiwsIwDMNIiAkLwzAMIyEmLAzDMIyEmLAwDMMwEmLCwjAMw0iICQvDMAwjISYsDMMwjISYsDAMwzASMtZrcE9sQiGoqICaGggEoLwcgsHxbpVhGMa4Y5qFRygEmzZBezuUlOh20ybdbxiGMc0xYeFRUQH5+ZCbCykpus3P1/2GYRjTHBMWHjU14PefuM/v1/2GYRjTHBMWHoEAtLaeuK+1VfcbhmFMc0xYeJSXQ2MjNDdDf79uGxt1v2EYxjTHhIVHMAjr1oHPB9XVul23zqKhDMMwsNDZEwkGTTgYhmHEwTQLwzAMIyEmLAzDMIyEJC0sRKRURL4jIltFZJ+IrAjv/2MRWT3yTTQMwzDGm6SEhYgsB94Dfg84CswHMsKH5wP/Z0RbZxiGYUwIktUs/hF4H1gIfAaQqGOvAh8ZoXYZhmEYE4hko6E+CnzBOdcqIqkxx6qBWSPTLMMwDGMikaxm0T/IsWKg4xTaYhiGYUxQkhUWbwA3DXDs88Arp9YcwzAMYyKSrLD4G+AaEXkKdXI74HIReQC4DvjmYBeLyMdFxMX5NMacVyAiPxKRWhFpE5FnRGRlkm01DMMwRoikfBbOuRdF5NPAd4H/CO/+e+AA8Gnn3JYh3uorQHTt717vBxER4FHUiX470ADcBTwvImc752yBCcMwjDEm6XIfzrnHgMdEZDEwE6hzzn2Y5G3ed869PsCxa1FH+qXOuecBROQ1YD/wp6igMQzDMMaQYWdwO+f2OOdeHYagSMS1wFFPUISf1QT8Clg3ws8yDMMwhkBCzUJErk/mhs65/xrCaT8RkWKgEXgS+DPn3KHwseXA9jjX7ACuFxG/c641znHDMAxjlBiKGer+mO8uvJU4+wAGExZNaGLfi0AzcA7wdeA1ETnHOXcMKER9ILHUh7cFgAkLwzCMMWQowmJh1M9B4CHgMeCnaCJeCfAFYG14OyDOubeAt6J2vSgiL6EhuV8B/gIVQi7O5RJnX+SgyK3ArQDz5s0b7FTDMAwjSRIKC+fcQe9nEfke8FPn3NeiTvkQeElE/gF1QF+XTAOcc9tEZBfgLUlXj2oXsRSEtw0D3Ode4F6AsrKyeMLGMAzDGCbJOrgvA54e4NjT4ePDIVqb2IH6LWJZBhwyf4VhGMbYk6yw6ALKBjhWDnQn2wARKQOWAF6OxqNAqYisiTonF7gmfMwwjElKKAQbN8K99+o2ZFlTk4Zk8yx+BnxDRPqAnxPxWXweuBv48WAXi8hP0HyJbWgk1Dlowt0R4Pvh0x4FXgMeFJE7iSTlCfCtJNtrGMYEIRSCTZsgPx9KSqC1Vb/bUveTg2SFxVeBGcA9aOa2h0Md319NcP121Al+O+ADqoBfAHc752oBnHP9InI1sAH4AZCFCo9LnHOHk2yvYRjjTSgEFRUce7KGFVkBuleV05kSJDdXD1dUmLCYDIhzyfuCRWQJsBqYDVQCW5xzu0a4bcOmrKzMbd26dbybYUxjwuMjNTUQCEB5+RQYEIfTqSh1YvMrfoozW8noaKR69To6i4P090N1Ndx669h0wRgcEXnTORfX1TCsDG7n3C7n3H87574V3k4YQWEY4403Pra3q7mlvV2/T2r7/HA7VVGhdqfcXPLyU2hNyaU3J5+83VoarrVV5Y4x8UnKDCUiCRMYojKxDWNaEjU+Akxec0u0JrF3L5SWRjpTXQ3PPQc/+xl85COwfr1qGrHU1KhwAU4/HbZsAXx+8huraW6GxkZYs+bky4yJR7KaxQHUQT3YxzCmNTU14PefuM/v1/2ThlhNorYWduzQ7e7dGsrU1wc+H7S0wIYNKlhiCQRUfQCKi2H1asjpb6XaBfD5zLk9mUjWwf0lTs6uLgI+BZyGrndhGNMab3z0JuGg31NSdIydFH6MWPWopETVgN274cMPYcYMyMiA9HQoKtJzHn74ZO2ivFyFDoDfj7S0kt7eSM1pa8gZq74YI0Ky61ncP8Ch74jIf6MCwzCmNTHjI62tcOAAOAfZ2ZMkbDTKfASoDen116GqSrWLvDzo6gKvtE5+PhyKY4EOBrWTFRU0fFDNlr0BOlesYcb8YFLvINa3HgzqvkkheKcISa9nMQgPAv+J1ncyjGlL1PhIdbUOZjNnqsWmu1vt9k1NOinfvBluvnm8WxyHWPWouBiWL4cjR/TT3Q1LlqjQANU6Skvj3ysYhGCQFzZCezB5X05sfsbBg/DTn8JFF8H8+ScL3ikZiTYBGElhMRPNiTCMaU94fDzOvfdCZ6cOYjk5OvB1dMCzz8KVV07AwSyeepSaCrffDpWV6qPo7VW/RWMj1NXBTTcNestYZcW7dXX14E2JtYhVVanlq6oKFi48UehA8ol/JlyGRrLRUB+LszsDWIFmWb88Eo0yjKlGIAAvvqiCwufTfSI6oE3IKKl46tGaNREpeMcd6qM4dEg1iptuih8NFUU8X87Bg6qo3HvvyQO1N4g/8gjMnauKTHGxamX5+br18ITOUCPRKiq0+bt2aZtWrVJN7/XX4Ykn4JZbEnZn2pGsZvECJzu4vdLhLwK3nWqDDGMqUl4OP/+5CgfnVKtoa9P9EzZKKlY9iqa8/MTR1Cv6NMj0PFZZeecdeOopmDNHnf+dnXD0qMooiGgIwaAKhi1bNJoqL0+Vmfz8yL29fI2haC8VFaoYFRXpc9vbtemrV6um0tAA990Hs2dPQCE+jiQrLC7lZGHRCRx0zlWNTJMMY2JyKuaKYBAuuUSjTxsadMBbsUIDijxNY9IyxKJP0crKBx/Ayy/r4DxvngqKnTth2TI9XlengVc9PaqBtbRoANauXTqI790LZ5wB/f36OC9fo6JCv3d36/Web2jFikhzH35YBUVRkWo2zqlAOXAAFi2CwkK1tE1IjW8cSTYa6oVRaodhTGhGogje2rU6iOXnR9wAUyIpLYksRE9Z2bgxMvCLRARmZaVqXc89pwKjs1MjyIqL9baHD8P556sVLBQ62UIG8MADeu+MjEjw1rFjamoqL1ezlxfE5fPp95wcaG7WfR0dES3FiJCsz6IPuMA590acY+cBbzjnUkeqcYYxURiJrOzB3ACTmmF4rj3trKMjIiiyslRYHDkC+/erkMjKUqFx6JAO5jfdBNeFl1eL51MIBjXybN8+TQfJz4eyMvXDe6al0lIV0kVF+n3PHtVAcnPVJNXWphrOZCtDMtqO+mTNUIMtbZpK/OVQDWPSM9xInlgGcwNMWgbKQvRG2zijWCAQPG56AhUMDQ2QlqaD95w5KiRqa1VDyMpSAVJVpbcb7B06pwIjPz8iiJyLmJbWr1efBeg5p50Gb72l2/R0FRSpqZPLwV1RocKwt1f/TqP9PyP19zakch8ikiIinsaQEv4e/clB1+CuHZlmGcbEIqpqxXGGXQRvqq0AVF6uU/XmZnUieEWfystPLhty+DD89V9zyRt/z+LtGymfHSI9XQfy3l6NQuruVsFRWanbhgY1VeXkqI8jXlWRaAIBFeJZUYH80aal8nI1Y82YoRrL4sXwz/8MX/qSCoy5cydwsmQcQiEVFGlpqil1d6sQ7utL/K6SIaFmISJ3A38V/uqAVwY5/Qcj0SjDmGjESzsYlr9hvFcAGg1bxWD2tY0bI/a72lodxdLSyHeNrF7RzuHtm8g8bR05q4PHZ/J9fSooPF9Gb6+ahpYvH5o2V16u4a8NDeqs9iLPok1LscFck5nNm9Xhn5mpv9Y5c1SwVlaeKDBPlaGYoV4IbwUVGj8GYqdCXcBO4Ncj1jLDmEAMyd8wlIF4PEvSjqagGsi+Fm2/271bR7GwzaloYS5FRXC2rwKu02s3btTZfV2dKiN9fXqpSCRbO5E2FwyqhnLffTpgBgKT07SUiFBIheJ996lCV1io0WMffqjVWRoaNBhgpEgoLJxzL6I5FIiIA37knDsyck0wjMnBoP6GoQ7EI+X8GA7jIaii/RleNl1HB02Sx87XoKnRz8z+amaG5WpNjc6Q58/X2XJLi4YZz5qlg/1QtbnycjXJTNXMbO/PbffuSFHg999X01pOjvosliwZWeGYbOjs/xu5RxvGFGKoA3EiZ/BoMh6CKtp+l5sLDQ20tvRT4VaQlgnFma20uACvheVqSoq6NbKzdZmMxkbVDlJT9RUlowRN1mCCUEhNS1u3qvnM74cFCyKDfzAY+XPr6dF3FQqpr6KxUc12dXXqlxnTaCgR+Q/gb5xz+8M/D4Zzzv3+yDTNMCYRQx2IR8z5MQzGQ1BF2+/y86Gujj0ZK+hIKaR9dzNVjY28t3AN1W/B9u16ic+nfob0dEhJOUZr6/20tLzLtm1N/Nmf5XHWWWdx0003EZhssa1DIBSC738f3nhDB//mZhUGzc1a5PeJJ9RMd+CAOub37dNM+NRU/XPq7lbN7JxzVJCMJEPRLC4Bvhf+OV4GdzQWOmtMT4Y6EI9nssV4CiqAggIoL+foE0JjbTW9+QH2nraG1w/rWtxz56qAyM8HkQpeeeUe6uufAMC5Tg4e1Nv84he/4O6772bt2rXcddddlMextUzW4oCbN2tme0eHCgivNMy2bRre672jo0f1V9nToyanvDzVKLzM9OLikU8qHIrPYmHUzwtG9vGGMUVIZiAeSftIMqPieAiqOL6crpZGtgbWcTQlyN53NeQzJ0ft7tnZ8O67/8a+fXfQ399BvPlnR0cHAL/85S958skn2bBhA7fddttgj5zYa4dE8eKLakLy6l45B/X16uAPBjW66dVXdR+o2S4jQ53Zfr8KiWXL1Hw10orXcKrObnPOtcY5lgOc55x7aaQaZxgTkoEG6AkwECccFcfKkO+9o6eeUrvIqlU6suXm0pUFvh0VtC7UdvT26uk5OeD3e4KiPeEjnHO0t7dzxx13ABwXGOMdcOb9aaSk6GDv3NC1m2PH1AwnokKgqUl/bmvTX6+Xe9LfryGyXqkSkcjCWn19KoBHOvIr2Qzu54ELgJPKfQBnhI9buQ9j8jOQQEg0QI/l1HU8R8XBiH5HIjpqeiVji4vpTPezOK+aPWGXTlqaDozd3RVs2TI0QRGNJzDKy8spKysbt4Cz6G6npsJLL2n3L75Yw4C9PxMYWBmcOVMjwNraVMa2t6uvwhMGLS2qVaSm6jsLBHTb26uaWVdXJLlxpP8ERrLcRybQdwptMYyJwWACYSIN0GMxKg7H+B/9jvLzdQTLydE4z+JiCtNbOZIXYN48DW/dtk0f09x8D729HcNqZkdHB7feeg9/+ZePkJKSdPWRk7p0qt3esUNNQqC+htxcLVWyfbveZ8GC+HONj31MfRDeelIiKhh8vhO/a5/VfzFzprYzNxe+8IXRW0xrKNFQCzhxbe0yEfHHnJYNfAmIswivYUwyBhMI45knASeOYm+9pR7OzEz1cJ5+emS6OVLPGo7xP/odnX66lpD1yoG0t7MoNUDNRTeQ2aWz4ZQU8PuPUVX1BMONkXHOsX3741RV1VBXF0BEB+RY91GiLoVC8NBD8Oij+lrPOAOWLh1anaXobnspJS0t8O67KmxmzVIFq709soZH7Fzj7LPh+efVlDRzpm7r6yNta2hQ2ZuRoaVPUlPVNFVQAHfdBVdfPazXNySGolncANyN/hYd8H1O1DBc+Hsv8Ecj3UDDGHMGEwjjmScRa+eordXiRitX6nT0hRc0GP+GG0bmecPVomLfkXMq1LKyaG6BtjbHtm2QvlCTyFasgJaW+0lJiWRsDwcR4Y037ufyy++kvV1n47Huox/9KLJOhidf8/MjNZQeeACeflpNYy0tuuzt9u1w2WUDd9uT32+9FXHP5OXpr+TAgUhBw3379HPkiGZZf/7z+vzoVf68YoD5+RyvmbVokf5JNjToq0xLU59Ffb0KoHnz4G/+ZvSz04ciLO5HS34I8BwqEHbGnNMF7HLO1Y9k4wxjXBhMIIxn+GmsnWPePJ1S1tXpKFVQoNPRkbJBDFeLin5Hu3Zpe/1+6k9fzWu7i8kvbebTUsFjBNmyRUtSZGW9S19f5yk1t7u7gwMH3sPvj6xC6ClhFRU68D77rJq+8vN1MN+yRfMV9uxRX/zhwzrzj1bYWlrgN7/R79H8+teaE/HBB2pyuugi/VN44QWV3++9p88+7zwVEq+/rkLD59M/m40btdx6SYlqGV4xwNNP1+sOHlShsHev5k90d+scQfNP9PuCBfBbvzU2ZUyGEjp7EDgIICKXAG/Gi4YyjCnDYAJhPPMkYu0cBQUaS5merobq/v6RNYcNo/T4cSe/944OH9bvS5fy4e5icnIgPdvPzIZqLrssctuOjiZGgra2BlpbdTB94AFtWne3mm2iI41SUvTnlhZ47DHVBrq7VYDU1Oiv3e/XmXxvr5p+6qOmwr/+Ndx9twqVmTP12sceUzOQiAqf5cvVT7FtmyqBM2eqUNmzR/0NTU1aKn3x4sja4suX6ys+dCgiGNraVIClpUXak5urz3nnnUi59dEm2XIfL45WQwxjwpBIIIxXHYnowTsvT0cc0J9h5M1hgwnNoUSFee+ovR1yc2naqqendbTSlaftLC3Vy6qq8kakyc4VcOCAzvZ37IhkNs+YoU3+2GkhFocqKO6voTsvwKHGcjo7g6xaBW++qYKirk4/ra36akUiipvHj3+s3xsb9ZhX3XXLFvUdfPCB+vQ/9Sltx3PPRRLtOjoiy8V6guDIEf21bt+uQiwrSwVcR4e2vbNThQWoYGptjaweOFZ/islGQyEinwS+DCwFYgvgOufcopFomGGMKxOxsFD04L1okab6OqchNN4aEiNpDhtq6XEY2J8R1ea8XD8phw9SWL2dtpLT8D25kSNV5SxdGqSm5ixqah6hv3/4pqjU1Gx6elbS2KhF9WproaQ3xJK+Chbn1dDcKpTWHOPMtQs4WF9CX1Mr5xzaxJzV64AgR4+qppGerrP35mb9vnixmpiKi7XbNTU6qJ9+ugqKtjYdzLu7I+Yjzynd3a336+lRLaOqSgWYV+wvENDzjhzRX6WIahVz5+p96upUYPX363Oys/V+fX0qMM47b9ivK2mSTcq7CvgV8AyaV7EZ8AEXoaaql0e6gYZhhIkevNvadAQT0ZHD5xsdc9hQSo97eP6MWPPU+edDKMQZfMCRvXtpmFUKtU30Pvcr1vQ8wa6Lb8FddCN79959Sk1NSXGcf/6N1NfroFzqQnyqfxMNLp9dTSVcIs+T2dpAOnNYsjSFzs5c9vTC4owKXt0dpLdXBcPhwxGTVXq6Dso//ak+Y/FidcinpqpD26vblJOjxzMy4JVXtH5TZ6e+hpwcvebll3XfjBn6a2tp0f0ZGfrqWlr02LFj+snMVHNZe7s+r78/YlLLztb7egmNYzGvSVaz+EvgX4E/AXqAv3DObRORJcCTwBMj3D7DmHKcUt2iiaLxDOTPSEk52Tz1xhuwbh0FgMvOpurZneyrzqExbTZz8xv42If3EVr8V8yZs5bDh3/JcMJnRYSysqtISwtw6JAOumfVVtCYmk9nRi59XUBfD2mBIgrrdnMgvZi8PPjYp/wcebOaqqpIBdcFC7Ti7cGD6qT2fAci6mz2zEGdnZFrmpv1+GWXqQw/ckSv9Qb0I0dUM6mrU9OSiL7Cnp6I9gGqDYmoltHcrOaojg79npqqz3JO23jWWfq6xyrFJ1lhcQa6AFI/+htNA3DO7RKRb6DC5Gcj2UDDGC9GoxjdZK5bdAID+TMyMgbNUSnsqKQ2L4f5BT58NdDVW0hOVyXn9lVQtfouKiufpLc3uQxugIyMbNavv4tDh3TwXbgQZjfUcExK6O3RAb4jNY/lp3Vwxqwmziir1RjanVWUFAR41YU42B0kPV3NS3l5amqaPVsH/I4O7WZHhy72t2iR+ix27VLFrqhIcySuv141gPZ2za8oKVFB4YXqLligZqaMDD0vFNIcjoYGPSctTZ/j86nDOyVFn9PUpK84P1/vcdFF+oz09JEvGDgQyQqLfqDXOedEpAaYR6T0x1HA/BXGlGC0BvWJlAB+SngmMa9Mqrfwwv79OtLNmKHe69NP1yXcPJ/H66/T0jubHL/uPnawg4asAPk9Ncwovo7PfnYDjz56Bx0dQxcYGRk+1q/fwKFDZezfr8KioAD6CgMUtrXS2J9LXh705JzOovwXoStN41jDKkPeGaX8YcsmZsxYx5vVQdLS9PddV6fdTEvT2X5vr870KytVgPT0wMc/roN3VpbKytpaNUMdPqyaR3W1DuozZujH59NUmPZ2FTqpqfqczrCrpqcnEreQlRWpJJubG7lnYaFeH7tU7GiTkuT5HwILwj9vBf5YRGaLSAD4KnAg2QaIyGYRcSLytzH7C0TkRyJSKyJtIvKMiKxM9v6GMRyiB3Uv0zY6eWu4eGGZ0fj9Yzc7HHG6unTE8vl09DpwQEfI6modObdsUXuOp5qlpZHX10BPl8Of2s68ojY6cudwpDtAIAAbNtzGP/7jBnw+HyKDVRdS01N2to/PfW4DfX1aRPDaa+GKK/SRb6aU4+9rZGZWM0WF/Vz+qQz8qxZHPNj5+WpvWriQggX5fHFJxfE6Tp2dKucCAR28GxvVj1Bfr7+vhgb9u1i2TDWQZ5/VarDf+Y5WjvUimvftU2Ewc2akfpNzeq/8fPVL9PfH759n5qqs1O3cuXquZxYb66Vik9UsfgKcGf75btTRHQp/7wO+mMzNROQLwKo4+wV4FFgI3A40AHcBz4vI2c65UOw1hjGSjFZVj/FMAB+IYZnbvFV6amrURlJUpKPq7Nk6subm6sg6c6aOpp/5zPHFsWf9030c3F9JRyBA/6JlzGtqISe9lt8pvJeCigC3XXMN5eXl3HPPPTz++OOIyPGy5ADZ2dk457jqqqu46667OHy4jPZ21Sh271azT3o69OQFaTlnHYsbKihJrSZ/TgCuvBEefzySCefh91PcVs3Nt8LNN+suL5ciO1vNRj092qWlS7Wrq1erItXYqMKpr08FQk6O5laUlkYc1lu2aKKeFy7b1RUp1dHdPfBrTk9XgdLXp7+bK6/UeIHxWKcj2TyLf436+c3wTH8tWhvqGedcbGb3gIhIPvBPqLP8oZjD1wIfBS51zj0fPv81YD/wp8BXkmm3YSTLaA3q473+UCzDMrd5F9XWar2Jw4f1wp4e9eJ2dWmn9u7VcJ+lSyM3Ky/H/63ZlGyu4NDWGprbUgikN3P2R3wUzPcfb0DZunU88sgj1NTUcP/99/Pee+/RUFlJQU8PK4uLufHqqwlcfjkEg2zbpgOvF3nU1qb9aWuDJZcGKS4O0tQML/vguiAn/nJra2netpu6HVU0ZgSokhAr1wYJBrU7a9aoxtDUpAP3ypUarrp+Pfznf2oX+/tVC9m/X6/p7VXT1e7dqg3MmaPXHjgQiWpqbdXzEq1mN2uWRlZ1dcGll8KNN46fuVKcG5/F7UTkXmCRc+4yEXHAN51zfxE+9mPgSudcacw1DwAfd87NH+zeZWVlbuvWraPVdGMaED2IRg/qI+GInkiruG3cyAmzcm9QXLEiMsMe8KIdO3QUO3RIX1BTOAu7sjKSWeb36+h4/fWwdu3JHfXuFR642b1b414DAbj99sj5Ub+Q2k4/rz3dyoF3Gnkhdx1tBUEyM9W809enlxcX62Nzc3XA9cyIX/ta1L36+mh9Ywf7DqSSldFHx2nL6epNZefidVx+Y5Cf/ETNSH6/dqWzU7t52ml6ny9/Wbv54Ydqlqqt1QgmzxHd0aEaSWam3uPwYfWl9Pfr+e3tA5ugQM+95BK9V3Hxia9jtBCRN51zZfGOJdQsRMSLfBoKzjk3lHt+FLieOCaoMMuB7XH27wCuFxG/lRwxRpNkq3oku2DdRHFm19TobPfxx3XM7+jQgfH99wcpde3Z6E4/Xe0rBQWR1OSjRyPqmBfCc845Kli6u0+Wtt69PP9GTo6O7tXVJ6o4YSdSbXcuv34ctm/PZWYWnNtfwc8qg+zbpxYwv1+Fek2NzuhBK8c2NKjvQHMSwr/c73+fmspepLCItgWn05NXTFp7MwtrK6ioCB5fN8Ln0/t4a4PX1wMVFXz69Ycp6jxCyJXys771dOeX0x72yzc3q9bQ3q4DfVVVxHHtLYg0GN7iRWvWaBsmQrTcUMxQf80Irq0tIunAD4ENzrkPBzitkPjOcq86SwFwgrAQkVuBWwHmzZs3Im01pjdDHdQnczhsIAD/+7/qVvD7NbyztVVNKg89BH/6pwNc5NWbWL06opI0NqqPwu/X0bKwUF+KZ2/xIgSiX4p3r927VVD4fDrClpTo+Zs3qz/kkUdg7lyOHA2Q/XYta3qb6JFc2vvzkfD6Dk1N6iPIzVWZ1dioj+rs1Bn8ihVRjw8GYdEiPmy5kPyClOPui95sP3n11XxYEynn0d6ufouODtVczmipgA0bCOYVsbdvHvm9jfxe5QZ+Wv97LO/roYgaDrYFeDutnJa8ID6fKlupqSove3sjy6F2duq9u7v13l6RwLlz1ew0d+7EWT98KIUEvzHCz/wa6uP45iDnCPEF1IDhEc65e4F7Qc1Qp9JAw0iGyRwOW14OX/+6ahPZ2RF7+5w5GuETV1hEO14KC7X6XWmpahCHD+unpUVvlJmpo/iSJfEjBLx77d0bqbGRkgKf+ISOpM8+q9X5gkHYtYvgK5voby+iY0YJXR0tzOiuI7snRE5OkK6uSDJ7To42ISdHm7BiRSSC9ziBAEUZrbR15h7XHtI6WmnK0MisQEDvVVWlmkleHsyfDxc//p/gGlmU2oq4Go7IHHpTMvlS6/f4Zf6NdKaXkNfTytX9m9jcto4j7UH6+lRgZWZGku497aKvL1LDavlyDcVduTJsMptAJF0b6lQQkXnAnwM3A5kiEl30NzPs9G5BNYjCOLfwSnk1jGY7DSMZxns9pFMhGFTh1t+vY3Nmpg6SIhEXRNyL4tnoNm+GrVsjRYw6O1UIFBerySpehEAwqOE9P/2p2omysnRUf/ttbUxJiTYwEIBNm0hNSyEjpZee7jZy26t4xXcFpzVU8K4Ej68b0d6uA/CsWeqI9mhujnl8eTmLd27i3bfq8HVV4u+opqcvjT1lt3BhOBz16FEdwD2/VfrbFcz/4CnI9ZHt97OwqJfUQy105qTgz2pnwfJctm+HjvRcMgTKXQV7O3XG4JmfvMjd3l792VufwvO5LFmiMQETjTEVFuiKe1nAg3GO3RH+nIP6Jj4R55xlwCHzVxgTiYkYDpsM556rLoXCQm8tbLXLL18+yEXxbHTO6ahaWKjxq3v26JR5wQK98UBhX++8o0LBK5IEHM+u+4M/0O91dVBURJavj4KuVkI9QSp7C1jY8g553bvpBna0lFNVFTy+1kN1NTz83RDn9FYwK62GjJIAF3wxCBtDx51LBaU5nL/5J7Qea6M5exbdS1dxRf4bFDAbgsETZOJcCXHxkftIz/er9tPfj6+lBl92gFk9B2hbuJTCQv07yMyEpgY/s1KqSU/X1+At7NTdrcIhJUU1lfC6UMydq/uOHIHf//1T+52OBmMtLN4GLomz/3lUgPwY2IPmWNwkImu8sugikgtcw8lhtoYxrgSDkRXOSkrU0ZqaOn7hsMnypS/BX/+1KgJdXTrm5+Xp/qRwTl/GM8+otMnJ0cp7/f0nFjqMjQZ48UVdyGnOHDXue06C+npNh541S0fQWbPIamsjb1YQt3cOhaEPSe/pYEfaEgKZ7azt3sSmynUcIUhWFszsDnH2oU00p+STMreEJU0HmfHDn8IVF+koffAg/O//kr1oEdkfm0ugsxPaKmFGAVRUECLIE09ozoRzsDClQkf8c87RelcikJaGv+MY0tNF88KzaWvSgb+7G/LTWqntDxwXXikpekxEv/t8KiCysyOvr79fo60movlyTIWFc64RXXXvBMKZmgedcy+Evz8KvAY8KCJ3EknKE+BbY9Naw0hMKKTjxooVarI4dkwnwbfcMkb/8CMQh1teDn/1V/Dwwzoml5aq+SbpzODGRhUUhYU6Cra1qf/iM5/RJeG89sZGA+zfr1Px2bNVSjU3axW+ggK10zQ2qhCZMQNaWshZNpfTK4+Qc1ovTS1ZSO5SqM+ltQHKeitozVf/xVndFVR15OOfk0tmFgRTqqjsKmJxVZUWj6qqUhtQe3tkNSSAykoaOrK4f4cqRwUFOsCHXqvh7YwAq5b2MOP883XRipYWcjKEdxZ/HjKKKUhppirVT3FGK5n+Rn7Ru4b0sGM8I4PjvovcXJWPfr8uvAQR89lENEHB2GsWQ8I51y8iVwMbgB+gpqvXgEucc4fHtXGGESY6iXnWLP0nv+giHetCoTEowzCCYVjl5SPQ3v37dUTMyNDR1ft5//7IOfGiAZYuVd9GXp5Os/fvVzXtnHM0A273bh2x6+u1lkdHB7lt2+nO8PPu7E9AdjFN+wH8lIiafZqaoDSrhvqeEmaI3vLimU00ZeVHnDFNTZEl6kB/cUeOQGUl1TP76SkMUVQUpKQnRLCygvntb0FTN/WumxnlM7XEbH09mb29BG/5I955GzJeqmBudTV12QFqF6yh5a0gGWHzk1e9JDtblai+Pn3cokV6rK5O/RVjVb4jWSaEsHDOnRTlFF7P+0vhj2GMO9GTeBHVIrwk5tpaXWlt1iwdCPLzx6BBEy0Mq6VF62ZXV6tWAZHaFxs36igYLxrgwgv1Bfb1qUBobVXN5LzzdDD3QnTff18FS00NGZc5dh4rpap5IccqVUHI6m6lNiVw/PdT3R8gt7cVkVxyciDUmsdpGY2Ql6/P9bSY1lZts1ezIyeH2vRSzty9ibpF5zOv6g3o74PUFGZV7SS9AzgtVW2Nqalwyy30zw7SGYLai4PIx0AaoOo9lXl5edqtjAz925g9W7UIr1bUzp0acbxmzSC5LROACSEsDGOiE5X0S2Wl5o85p6bv2trImsltbTpRjSSAjWKjJloYVmmpCowzztBB+MMPdUScM0fNUU88ockKRUUR24uXsT1njtry+vsjFWuLiyP3bm1VQRE2Z+WFQpz1wCb2PtdMb7efOf5W0tsbeUPWIK1qvfpNdznXdG6i8RDMXOTnSNssLsrYC7PO0OfMmqUazYUXahXaw2GjRXk52TNmULx7Bx/f+F0y+rpwqamEis7m8Kwylspuve6GG+DKKwkRPGm974wMfQ1FRSq4KipUYBQXR1bUCwRUHl55pVraJrKgABMWhjEkKsK+zZ07dbKclaXCYt8+/efv6orEz2dn6yR61Cf4Ey0Ma/162LBBf66uVkHR2QkXXKAvLi1NBUFjo6aMZ2TotDs9Hc48U1/iunV6/aZNKnAGKqAVDFJwwzpKQxUsKa1mZ02A/3p/DY0SZIboJQ2+IK9mraOcCqS6mvyPzCXn9jtUildXq/Zyxx0ajVVbq874+fMhI4MzX30At/Mt0rvbcaTQn5bOwo6XSD39CnIuLVNHeFERIYJ885t6i5wc7VJXl8q/FSvgk5/UiUVhob6OvXt1QhEIqKyaO3f8FcKhYsLCMIZATU1kHQMv6au7Wwel2lodDLq6dLBwTo+PetnxiVaVsLxcB9+HH1ab3Jw5Wg+qrk5fSHa2ZretWaMZ2fX1OjiffrpOuZubdcS87rrBa61E2QNzcwNUnnkVsxcGOe3nMCtcsiorS2f2eXlBPmwM0loKeRcB5ZzsFAiF1Nnk1fZobsb3/tv09bXTm55Of68jpb+H7P4uzjj8FGkH2mHuXBp21bDpqC6AlJOjZknn1HHtnAqQT35SLWgvvqjyMiNDu5GVpdqot4b2ZMjLMWFhGEMgvG4Ps2frmNbeHkk6TktTcwPouJeersE8oz5mJ1vAaizwPOXRBQI3b45U1svLU8HgleS54ILItdEj5kC1VmKc+kurDjLjZ0/QXXoaV/QsZWdOOccCQVav1hm8Vy394osjeQ4nUVOjKdPeYiV79kBrK6nOQUYq/a4X6dOKf9LRpjeurmZffwr5H9Xfd11dJAS2oSFS/Xb3bu3inDm6bW+PrHC3bJleB5MjL8eEhWEMgfJyNbkfPqzjWUqKOiUrK1VgtLerST03VweJY8fGKKplIlUljCZa68nN1RHUK9AEOsWOZSgjZrRTv7aWwqM7ST8tjSOtjeSlt7Ni/ya6S9fhCoMUFakQX71aH+dFxp5ESopK9/Z2dXJ/8AE4R19qGl39GaT3d4cXxu6nr0/oKFmEP6ObllaH36/WqwMHVA6mpenvPydHn11Vpd32tt5ko7pavzc26vfxVAiHigkLwxgC4XV7+PrX1fwUCEQSqmbO1IHCGxPT07W09EQcwwdjREunR2s9+fk6hfYKNHl1N5wb3C8Rj2infrj44IzibM5oaOCMK3Op2w8zP6jgPyqDlJRoHwZLHicU0pG8sVFH94YGzfmYMYP+ti7E9YKk0NvXTwopNPrmcLSxmLM/u4jcij6OtKop6d131bff2anPW7hQtcz2dhUMWVna1cxM/bvJylLz1dy5J+YrTmRMWBjGECkv19D6xkYdGPLyVJvYtUsHhSuuiIx5a9eOd2uTY1Qq50ZrPZ4k8sxlN9yg+5M1oUU79ZuaTjRvAUXz/VyRVc2ZV0UE34CDcXSijN+vdTdaWnQEz8ykY98x0nva6e/vR3A05M7l1ZV/SFVtIYtee4clXV1UvbiRzhXlXHNNkMcfV2Fx7rn6dxFdWvy++2DHUyFW7K6goLeGhrQAvkA5yz8WPJ6vONExYWEYSbB0acQU75GerslVE8VtMBxGPWVjIHNZsjePNm+JaOHCjg7N76it1Wl9IHDy40Ih2BilNgWDmnrvJcp0dan9aMWK406IugIhdcc7ZHW3ICJUzj6P0rp3WNZymMbUYubefCWr29o5vH0T7aet44tfDCKi5qW5EqJcKih+XJ9XuDfI5zLeINSVTyUlFNLK5zI2sb9uHTA5/lhMWBhGEsQLQEpNHZtVzEaTiZayMSCeeWvzZnUgdXdr2nNGBrzwgv7saS0e8dSm++5TwVBSooLCc2h0dakpKiODohUB3jycij+9k57sXLrJoqj2fbLzMunoz4SUFIoW5lJUBGf7KsJrtsY8z6/PO/fN+6iZvYKFq3JZCEAuPXWwqKECExaGMQWZiAFIp0oopJFdr7+uk2wvknVEI3RG0iESDKp/4fOfP3E92IICdSDF3jee2uRlVy5ZookQoA6Fhga9RyhErjtG/ukzaT9UQ2ZzHS6/hCJfFz2+YtJyffrc4uKTpWqc5wUK+6irqqQ1sPD4Eq1tfX4uLJxo0nhgTFgYRpJM1ACk4eBNgktL1dfS2KhCY/ly1ZiWLNEo2CGP8fGEQmXliWV5Ozu16uKpOEQ8VSglJZLp3d8fXxWKpzYFAnruhReqdHz1VW1nSYkmaKxaBbm5zNtdS/u/v46/rRInubT5A/S3tTE7pxWOdOm9YqVqnOf5FwY4g2rey9R3nJcHK+e3UjB3gsfLRmHCwjCmMdGT4BkzdLJcXa0+mPXr1aw/ZKd3KMRJdS+8Mhr5+TqgdnZqdtqyZafmEEkmez3euXPmaFLg/v0aoVBSokJn+XJ47TXVXLq7KXrrOWZ0h+ju6aKvoYvslDRyirLITktVZ3i8uNfY59XWQk0N/qO7uWD2s3DuSg2HamyE8gkeLxuFCQvDmMZET4K9mn3eBD0UStLpvXmzDrzFxWoS6uxUadPdrYs0iJxQBpysrMQNHMh85TmP6ur0XtXVmuRwyy0n32MgR9Mtt2i2eW+vCgfP/rZvn+ZdhMvCZvgyyHA9kJYKma36nO5wydjf/EbDnwZ6XmcnvPSS9v3aa/WaZ57RsLrJsEh7FCnj3QDDmO6EQmrqufde3YZCY/dsbxIcjTdB9yJKo/H7ByljsnWrDro+X2R9CK+6YkdH5LysrIjDZzA8G1l7u0q09nb97lVoPP982L5dMyBnzlSH9RtvnPwCPUeTz6fP9fn0++zZ6uvo6dGihy+8oFpFaanec8cO7bDfrwKip0fNZ9u3RyKnrrxS7+e1K/Z5FRUqONesUfPWZZfpmuJFRZNKUIBpFoYxZsSbJMMo5DckwWDlpSoqkqxTKKJmmcOHdWD3+SLahFey3KsPlZaWOMU9UTxvKKQDdlWVDvpVVeqhj6f6xDqaPEHU06M/e1Uh9+7V0NkLLlAzVVubvphjxyJtaG7WPjQ0RErJRrcrmtZWFRbRTMgws8SYZmEYY8BAk2SvbFJurk7Gc3P1u1emaLQZaNLtWXq8chT9/RHz/IBj/MKFar6prdVBdOdOrbPk96uPIj09Uh9lKEsJJlJtdu3S2X9Xl760ri79vmtX4o57gig7W81kdXUq2Lq61Px04ABceqkm6Pn9+otJS9Nzc3J0xaLeXnXyxLYr+pcdDKog27JF3wtMjkJQcTDNwjBGEU+beOopjcxctSoiFABefvnkbO+xnngOli+XVJhwQYHOsg8d0k7m5ESqzfp86rdYvXroYbOJnNj19ZFKsaDbjg7dnwjPWeOc3r+7O7Lm6dlnq0ZRUKCFn557To/l5qomIqJSs7c3sspedLuiNaKlS1VQpKSoEBu09sjExoSFYYwS3gSzrk6Dgjo61Cx+7bU6Xvr9Ou5MpCUpYkkqTNg5WLBAB/C+Ph28Z8/WgbeoiJPqWiTKvUhUgr2gQL+3t6tA6ggvdh1r9omHJ4jy8lRQLFig2/R0/Xi1q268UaOefvMbVa1KSyNLxba2avXcZ59VM9Ull2ifYqMGVq9Wn8jhw+pnmaSJOSYsDGOUqKhQQfHrX+uY5pyOZw89FPFRnHuuHoME9fRGtMrfKBEIaAdXrtSZNOhA7nnLo4mXVf3AA+qodi7Sx8FUm6VLVSBVVan/IC9P18eYOzdxWz1BNGuWCon334+Uhc3KUo1i715dpGnxYq1Em5oaqUOel6cmq/ff11yNyy7T6zZtUhUyegZQXKwCZvXqkwXmJMKEhWGMEjU1GlzjCYPWcNRle7uu/bN8uVpmiop0jPVWUDtp4hk9sKam6ko6P/+5zmTXrh17oTFYOOsTT+jAXVgY6dSyZSerSrHO6+5uNdPU1Gi/oj39Aw2w5eUanbR8+YmSdii14aPLhnjLHubnq/Cpq9P7XHFFRJC1t6sauGePCoOzzlITW2enCopo2tuHOAOYXJiD2zBGiUBAxz9vBb38fN3vjYsrVuhqoj6fnnPVVTounjT2ewNrd7f+nJqqg9iOHSeGbI4FicJZb7lFbfmVlZEVflJTTx7AY53Xu3er1OzpGbqnfzDv/FDwyobccAN89as6mM+dG1msZOFC/bm7W9uVlQWf/rQKs6wsdVjHCkG/XwXPqbRrgmKahWGMEuXlkbW5U1Iiy62mpKiGsVAryiVOdvNs4Fu2RNZ1dU5n8N6AOlYDUaJw1vJyNeUkMpnFOq+bmiJrcnsMxdN/qrVX4pUNeeIJbdtrr0VCcmfP1oS6zs6If6S+XpdjjcZzOE2lmjBhTFgYxigRDKr28Pbbao3JztZPa+vJZvVBx0VvYPXWb4DIGg5jHTo1lPK0Aw2UFRWaMX3kiNYWyc1Vc47fr1pIQ4N+9xgLT3+8iKv6ek28O3pU3/exY9q+hQsjxQZzc3XVo9TU5BdwmqSYsDCMUeSzn9Xx5NgxHd+9SM/UVJ24DqnCq+eMTU/Xm4hEMojHOnQqmZpMHqEQ/OQn8OCD2vH8fB10+/rUDJSfH0muy8hQR/NQB95o/4mnvkU7yGMT8WI1ntiIq3fe0Szwvj6OpaVx/6FDvHvsGE1A3pEjnHXBBdx0+eUEvGTD8vKpVYJ4EMQ5N95tGHHKysrc1q1bx7sZhkEoBPffH8lTO3RILUn5+aoY9PVFKrwOatYOhdQZ++yzOrNfGVWMbizt4Ses1RA1mx6oDd75v/iF+jGysyODeVubahI/+EHk3GQivqLbEl2D6eKLT343g7Ub9Lm7dsEzz1Bx9Cj31NXxRDhruzNqjMzOyMA5x9ozz+Sub36T8quvPvV3OoEQkTedc2Vxj5mwMIzRxRsDn3xSx7BVq3S/V+G1uDiJxZMGm0l7g+Joh9cmM6hv3KhO8L/7O5WSmZnqLE5NVa2isVEH+eHg3Ts3V9W0rnDJ8MxMLdfR3Kyz/+uuO/Fcj+Zm1dSKirQve/fyb888wx3vvENHXx+DjYwiQnZ2Nhs2bOC2224bXvsnIIMJCzNDGcYo45nwo32pcGKF1yGP6d7NYsuBd3WpreuKKzTXYASKTA0oE5Jx3nqdTk/XKKnMTPXud3Zqu73s6+EQ7T/x/Dki6lOAE30psb6W2lotfFhRoXkSK1fyby+/zB1vv017f3/CRzvnaG9v54477gCYUgJjICx01jDGiMEqvCaNVw48NVUzlmtqdJDcvXtEikwNFiGbFF6nV61Ss1N7e6S0RmOjhqEm2zCvRO/evXDwoO7Py1MB5Dn+4cSXG/3ya2s1smzPHu1caioVjz3GHRUVQxIU0XgCYzpYMkxYGMYYkXRhvsGILQfe16eJcF5hO0hQT3xwoiNkT0n2eJ0+7zxNKuno0CJ91dUqOGDoEihWgpWWwiuvaOmNRYtUCNTV6c+xLzf65e/apZ3q6NAIJ5+Pe957j47e3iQ7p3R0dHDPPfcM69rJhJmhDGOMGNH1u0XUV+Hh82kNI5HIvlOIlKqpgfmpIQp2VJDZVENXXoCMReUcbItpbCL/RXSnzzxTB/OsLN3v86mG9OqrusbD2WcP7nOJzfHwElWOHFEBcfHF+k68ulTRLze6HYcP6/ezzoL0dI61tfHE/v2D+igGwznH448/Tk1NDYGJUtRrFDBhYRhjyIjlap17rs6qU1I0wqigQAfB5cuTCz0dgLkSouDlTaQV5dNVUEJaRysFL2+i/6J1QLgD8eo7xfOTRHfaix/u7dXiellZ2t7XX9cV5C666ESfy/nna6LKtm2asX7WWaqleAl08+frPW69NdImT3h5alC0wPB+9sxhW7Zw/86dw3pH0YgI999/P3feeecp32uiYmYow5iMrF2rBe76+jQm1+fTmfUFF4xIiYlyqaDR5dNMLv0uhWZyaXT5lEuUHWootqpoH8OTT2pbs7I04S0rS5Pz+vr0e1GR5lp49+rrg3/6JxWKWVl677ff1pLh8daGGKqjxTNJZWRAeTnvHjtGZ1/fsN6TR0dHB++9994p3WOiY5qFYUxGgkEtnz1KlWiL+2s492Ml7N6r42peHqz4mJ/ivqhM7XjZ3J2dukhHTY2axI4d0/LfJSU64HvaRHu7htJ2d0cWFcrPj6wPAZG1tc8+W4XfggXqJD927MS1IZYsUYE00KIhseVQok1SbW00xS6wNEwavCisKYoJC8OYrIxm/aFAgOL2VooviM5LaAVf4IRzTsjmrq3VnImCAhUOzz+vEVpz5ujgvWqV+hd271bne3Oz5lzMnq2CorExUs4EVFCkp59YFnzZMr3eWxtiyRLNuPbCZlNSNNJpyRJtT2Ojmrni+VLC3/M2b4Y33zzlV1YwlHU0JjFjaoYSkU+KyHMiUiUiXSISEpGficiymPMKRORHIlIrIm0i8oyIrBzLthrGtGYooVux57zzjg7Y3sy+p0dn/7/+tTqyd+/WAT4Y1MG9tVW1hUsu0cG9rk7Xl/Cel5amEU8dHZFnZmTAGWdoHZXrrlMTk2cK8wRGf7+avLq6VNPw1pkYIOrqLL+frPT0U3pd2dnZrFw5tYeosfZZFAJvAv8f8AngLmA58LqIzAcQEQEeBa4Ebgc+C6QDz4vI1Cy6YhgTjaGU/449p6tL/Sae81lE1+HeuVMjnh57DP7nfyIlOW64QX0sfX1aWfGOO3TrPe+WWzTiqa5OBUtbW6QseDCopqdHHtGif7W1WmirrU3NV54PoqNDhdcgcb83RhcvHCbOOW688cZTvs9EZkzNUM65/wH+J3qfiLwBfACsB/4RuBb4KHCpc+758DmvAfuBPwW+MpZtNoxpy1DMXNHneCU1PFpbdTbf16eO7I4ONUOBRmnFq20Vm3Qye7aWDN+2TcNiL75YB3/P9BQMqp9jyxZdiW71avjv/1bzVWamFiiMTpWPw8xFi1h71ln8cts2hlP+SES46qqrpnTYLEwMn0VdeNsT3l4LHPUEBYBzrklEfgWsw4SFYUw8QiGd3T//vC4ctHKlVk3MylKfQ3+/OrFLSjRsdu9e1Srq6rRuVH+/ahznnnvi6n/egkrRbNwYMT0tXaqCIiUlsqLU4sW69fIwYPCck/Jy7rr0Up587z3au7uT7np2djZ33XVX0tdNNsYldFZEUkUkQ0ROB34IVAE/DR9eDmyPc9kOYJ6IjEzogmEYI4MXsurzRZYYfeYZ1SgCAfVHLFqk2kVOjvoimppUuLzxBrz1ll6blaVhsvffP3hWd02NRl299ppmsqelqaA5fDhivvLWmRhKqnwwSPlXvsKGG2/El5GRVNd9Ph8bNmygrCxu7b0pxXjlWWwBuoBdwFmoyelY+FghEC8GrT68jRtyICK3ishWEdlaM8wSB4ZhDIPofIuZM1VgXH21Ri6lp2tmuXM6+29r07IkeXnq8PbCfnNyNEKqqEiFyGB1RVJSNOqqqyuyNkZrK3z84+r0Li9PflnTYJDbfvhDNnz3u/h8PiQ6Ez4OWnXWN+Wqzg7GeJmhfg/IBU4D7gCeFpGPOucOAAJxM+8H/e055+4F7gUtUT6irTUMY2AGWj1v/nzVJt54QyOjcnN1m5+vmsYrr6gfY8mSyHXZ2Zq4N9iEz7lIWZPobbS/YZhhxbfddhvl5eXcc889PP7444gIHVHRWBkZ2TjnWLXqKj7/+bu47bapr1F4jIuwcM69H/5xi4g8ARwA/gz4MqpBFMa5zNMopnbmi2FMNqLzLWprVWOoqtL9n/ucCoOtW3VAv+IK1R48E5WIhsN6dHTo98GcxZ6je+9ezePIy9Pvp5iF7VFWVsYjjzxCTU0N999/P4888h59fQ34/QUsWLCSyy67kRkzAmO6mu1EYNwd3M65RhHZAywO79qBhtXGsgw45JxrjXPMMIzxwluatK5O6zelpqr5qbRUtYp16+Dmm0++zluTY9cuFSDO6eC/ePHgpXgDAY26uuCCyD5voaMRJBAIcOedd7J4sbpDqqrU1bJzp6aDxK6jPtUZ99pQIlICnAHsDe96FCgVkTVR5+QC14SPGYYxkfDyLY4c0Uin/Hz4yEc0GmmwuubBoOZaXHyxDv6dnVpI8MYbBzchjWit98QEg/D005rkvW+fbp9+esoutT0gY6pZiMhGYBvwLtAMLAH+BOhFcyxABcJrwIMicidqdroL9Vl8ayzbaxjGEAkG1Q9x4YWRpQDhxNXqBrru5pvjax6DXTNitd4T88476kMXiSz2l5Gh+0dJPk1IxtoM9TrweeCrQAZwGHgBuCfs3MY51y8iVwMbgB8AWajwuMQ5d3iM22sYxlCJrRUFp7SmxqCMZl2sGLZuhXnzNGDLo61N9ycj4yY7Y53B/Q/APwzhvHrgS+GPYRiTAc93AapReGtq5OXB176mZqrSUli/flJMySsq4OGH4dln1R1SVKTBWj6fdukUy0lNOsbdZ2EYxhQhXj2p+fO1/EZLi07PW1pgw4Zhrw0+VlRUaDNbWlS+VVaqY7uzU2Xge++dmCA+HRj3aCjDMKYQseahr31Np+RFRfrd2z788ITWLh5+ONLsnBzNI+zs1OW+V6xQuTfFK5KfhGkWhmGMHkeOnLhGBeh3r6DgBCW22YsXa03DtDQVFldeeWIO4HTAhIVhGKNHaan6LaJpbNT9E5joZvt8KiTy87U+4gUXaBmrKV5k9iRMWBiGMXqsX6/JenV1mmHt/bx+/Xi3bFCim11SokuE79mj2sSzz8KBAxPaijYqmLAwDGP0KC/XRY1mzNCS5TNm6PcJPtJGN/voUc2tOPdczdwGFRqVlVot/d57dTtYodypgAxnsY+JTllZmdu6det4N8MwjCnAxo0nlvvIy1Mz1FtvaV3ElhYVKsuXw+23T+7MbhF50zkXtzqiaRaGYRiDsGuXlrzyKqJ3dekKsVu3arWRmTN1++KL8NBD493a0cOEhWEYxiDU12ttRJ9PK5n4fJFgrqYmDadtalJt49lnx7eto4nlWRiGYQxCQYFGRrW3awZ3R4euENvfr8tu9PWpMElJOTlKeCphmoVhGMYgLF2q/ojMTK2gnpmp1Ux6elRgZGTotr5ej01VTLMwDMMYhPJyjYhavjxS8mrGjIh20d2t0VFepvdUxYSFYRjGIMSriH7WWapZHDyowsNbRXYqlwAxYWEYhpGA2JJXtbW6hPiFF0b8GHV1mosxVTGfhWEYRpKsXav1ovr61FfR16ff164d75aNHqZZGIZhJEkwqKu/VlRoRFQgoL6NyZyQlwgTFoZhGMNgDBfrmxCYGcowDMNIiAkLwzAMIyEmLAzDMIyEmLAwDMMwEmLCwjAMw0jIlFzPQkRqgIPj3Y4oioHa8W7EOGD9nl5Yvyc/851zcReMnZLCYqIhIlsHWlBkKmP9nl5Yv6c2ZoYyDMMwEmLCwjAMw0iICYux4d7xbsA4Yf2eXli/pzDmszAMwzASYpqFYRiGkRATFoZhGEZCTFiMECLyf0XkVyJSKSJORL4xyLkFIvJdETkkIl0iEhKR++Oc92kReUtEOkXkoIj8hYikjmY/kiWZfkddc6GI9IfPP6ny8VTpt4jMFpF7RGSriDSJSI2IPCsiHxvgnlOi31Hn3iIiH4T/xj8UkS8PcN6E73c8RKRIRL4nIvtEpENE9ovIv4jISXkKQ30XExkTFiPHLcBM4JeDnSQiBcBvgMuBvwCuAO4AWmLO+yTwCFABrAW+Fz7/70a43afKkPrtISLpwA+B6gGOT6V+nwf8FrAJWA/cCHQCL4jI1dEnTrF+IyK3oL/nR4ArgZ8DPxCR22LOmyz9PgEREeBR4IvAt9G2fxv4AvBo+Lh37pDexYTHOWefEfgAKeFtGuCAbwxw3r+j2eW5Ce73FvBizL6/ArqBWePd32T7HXX+14HtwDfD56dN1X4D+XH6lwZ8CLw0hfudBhwDHojZ/x9opnP6ZOt3nD4uCff/1pj9Xw7vX5rsu5joH9MsRgjnXH+ic0QkB7ge+JFzrnmQ8+YCZwMPxhz6byAdncVMCIbSbw8RWQT8OfCHQE+c41Oq3865Rudcb8y+XuBtoNTbN9X6DVwABIjfnyLgozC5+h2HjPA29v+4Mbz1xtYhvYvJgAmLseU8IBuoFpGHw3bOVhH5pYgsjDpveXi7Pfpi59x+oB1YNjbNHXH+DXjYOffSAMenar+PIyIZ6ADyftTuqdbvuP0BdoS3ywY7b5L0ewfwEvCXIlImIn4ROR/Vip5wznm/36G+iwmPCYuxZU54uwHoA64FbgXOQe3YM8LHC8Pbhjj3aIg6PmkQkd8FyoA7BzltyvU7Dt8AgsA/RO2bav0eqD/1Mccnbb+d2pKuQk2KFajPcQuwD/hs1KlDfRcTHhMWcRCRy8ORHok+LyR5a+997wd+2zn3tHPuIeDzwDzgd70mhLfxMiYlzr4RYbT6LSKFwD8CX3fOHRvs1PB2SvQ7znO+CPwZ8DfOuZejD4W3U6Xfg/VnqOeNWr/jNmR47+I+4COon2JNeFsGPCwi3v/6UN/FhOeksEUDgFeBM4dwXnuS960Lb58Jz0wAcM5tEZFmVMOAwWcd+VHHR5rR6vffotFPPxOR/PC+rPA2T0Q6nXNtTL1+H0dErgHuB37snLs75vBU63d0fyqj9hfGHB+vfscjqXchIp9CI58ud849Gz72kojsA54CrkGj4Ib6LiY8Jizi4JxrBz4YhVt7dsqBZhn9MectB17zDorIAsAH7ByFto1mv5cBK4kIy2hq0X+qTzP1+g2AiFyGhktuBP4gzilTrd/R/YkeID37/M44541Zv+MxjHexMrytiNn/Rnh7Jvp3PdR3MeExM9QY4pwLAVuBT8TEYV8A5BL+w3POHQLeAX4n5ha/i0YRPTEmDR45/hi4JObzQPiYl28yFfvt/W43Ac8CvxsvmmgK9vs1dBIQrz/1wCsw6ftdFd6eH7N/dXh7JLwd0ruYFIx37O5U+aC2yvWo/8EBPwt/Xw/4os67DOhFE3TWoqG0h9HomOyo865CNY0fAh8H/gRN6Pr2ePd1OP2Oc903iJ9nMWX6DZyBDggHwn35SPRnqvY7fN6Xw/3523B//jr8/Y8mY7/jvIdcVCAcBW5DJ0C3oULkEOBP9l1M9M+4N2CqfFB7tBvgsyDm3LWoFtGJmmb+CyiJc8/PoDOvrvAf4F8BqePd1+H2O+a6uMJiKvUbzdge6Bw3Vfsdde4fALvC/dkN/OEA95zw/R6g3XOBH6MBK53h7X1AaZxzh/QuJvLHSpQbhmEYCTGfhWEYhpEQExaGYRhGQkxYGIZhGAkxYWEYhmEkxISFYRiGkRATFoZhGEZCTFgYUxoR+YaIjHt8uIi8EF2ETkTODrdtxKuOyhCXtzWMZLDaUIYxNvxhzPezgbvRRXEmTTE5Y/piwsIwxgDn3KQpGGcY8TAzlDGtEJFcEfkXETkqIl0i8qGI/ElMYcePh00514bPrRWRGhF5MKrEunduQET+R0SaRaRBRP4zfJ0TkY9HnXfcDCUiNwL/GT60O2qthAXhjwufE/2cj8e5Z6qI/K2IVIpIe/gZy4mDiKwSkUfDbewQkVdE5OLhv0ljumHCwpg2hBekeQy4CV2M6RpgM/Ad4JtxLvkeWvPoi2jxt8+G90XzC7TW113Ab6PVUr+foCmPoUXlAD6HLrN6ASeWsB4K3wC+DvwELfH+FPBo7Ekici66XkMhcEu4H3XAMyJyXpLPNKYpZoYyphNXAR8FbnLO3R/e95SI5ABfFZHvOOdqo85/yTl3e9R5S4GbReRG55wTkU+E7/dbzrmfhc97UkQeRVc+jItzrkZE9oa/vu2c2+Mdi1JwBkVECtAKrfc65+6IamMf8Pcxp38bLdB3qXOuO3z9k+i60H+JChrDGBTTLIzpxMfQ0tD/E7P/QSADnd1H81jM9/eATKAk/P0j6FrqG2POe/iUW5qYlUAOWiI8mp9GfxGRbHTJz58D/SKSJiJp6HKfz6DvxDASYpqFMZ0oBOqdc10x+6uijkcTG6XkXectCTsbaHDO9cScV31KrRwaswd4Vuz3QiAV1SD+Mt6NRCTFxVmUyTCiMWFhTCfqgUIRyfDMMWFmhbfxln0djEqgQETSYwRGyUAXDIHO8DYjZn9RnGd7z9oRtT/22Y2oNvWv6LopJ2GCwhgKZoYyphMvon/zn4vZ/ztAN/B6kvd7HZ21XxezP/b+8fC0lOyY/dXhYyti9n8q5vu7QBu6Yl00vx39xTnXBrwMrAK2Oee2xn6G0FbDMM3CmFY8AfwG+HcRCaAz8quAm4F7YpzbCXHOPSUivwHuFZFiYA+6vOiq8CmDzdi9vIs/EpEH0Ciqd51z3SLyv8Dvi8gu4ENUUHw85tmNIvJPwJ+LSAsaCVUO/H6cZ/1f4CXU+f5jVCspBs5FV6T7s2T6bUxPTLMwpg1hc8ungAeAr6EO7E+hg+mfD/O2n0HDb/8BdTZnEfENNA3SlnfQ0NdrUAFWAcwJH/4/aEjuN4D/Dd/z9pNuosf/Dvg9NGT2E+H7xT5rGypI6oB/RgXL91An+UtD6qUx7bFlVQ1jhBGRf0XX3y6M40w3jEmJmaEM4xQIZ1rnoSatDOBK4MvAt01QGFMJExaGcWq0AX8MLEJzMPajWdXfHsc2GcaIY2YowzAMIyHm4DYMwzASYsLCMAzDSIgJC8MwDCMhJiwMwzCMhJiwMAzDMBLy/wOYK3Ke2B/8QAAAAABJRU5ErkJggg==\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.scatter(blue[\"lon\"], blue[\"lat\"], color=\"blue\", alpha=0.3);\n",
    "plt.scatter(red[\"lon\"], red[\"lat\"], color=\"red\", alpha=0.3);\n",
    "plt.scatter(two_cities[\"lon\"], two_cities[\"lat\"], color=\"black\", s=300);\n",
    "plt.ylabel(\"latitude\");\n",
    "plt.xlabel(\"longitude\");"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- For the cities at the two big cicles, what is the _distance_ between them? (Ignoring the curvature of the Earth!)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>lon</th>\n",
       "      <th>lat</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>36</th>\n",
       "      <td>-82.249594</td>\n",
       "      <td>31.402780</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>317</th>\n",
       "      <td>-92.341701</td>\n",
       "      <td>42.673137</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "           lon        lat\n",
       "36  -82.249594  31.402780\n",
       "317 -92.341701  42.673137"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "two_cities"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Subtract the two cities:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "lon   -10.092107\n",
       "lat    11.270357\n",
       "dtype: float64"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "two_cities.iloc[1] - two_cities.iloc[0]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Square the differences:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "lon    101.850624\n",
       "lat    127.020947\n",
       "dtype: float64"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "(two_cities.iloc[1] - two_cities.iloc[0])**2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Sum them up:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "228.8715706068982"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.sum((two_cities.iloc[1] - two_cities.iloc[0])**2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Take the square root:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "15.128501928707223"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.sqrt(np.sum((two_cities.iloc[1] - two_cities.iloc[0])**2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The above is called the Euclidean distance. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.metrics.pairwise import euclidean_distances"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 0.        , 15.12850193],\n",
       "       [15.12850193,  0.        ]])"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "euclidean_distances(two_cities)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Finding the nearest neighbour\n",
    "\n",
    "- We can find the closest cities to City 0.\n",
    "- Let's start with a subset of 4 cities."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[0.        , 0.25726537, 0.54650006, 0.60587695],\n",
       "       [0.25726537, 0.        , 0.31414736, 0.34945028],\n",
       "       [0.54650006, 0.31414736, 0.        , 0.16437124],\n",
       "       [0.60587695, 0.34945028, 0.16437124, 0.        ]])"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "dists = euclidean_distances(cities_df[[\"lat\", \"lon\"]].iloc[:4])\n",
    "dists"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(4, 4)"
      ]
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "dists.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "These are the distances between City 0 and the other cities:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([0.        , 0.25726537, 0.54650006, 0.60587695])"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "dists[0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0.0"
      ]
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.min(dists[0])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can find the smallest with `np.argmin`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.argmin(dists[0])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Whoops, that just says city 0 is closest to city 0..."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[       inf, 0.25726537, 0.54650006, 0.60587695],\n",
       "       [0.25726537,        inf, 0.31414736, 0.34945028],\n",
       "       [0.54650006, 0.31414736,        inf, 0.16437124],\n",
       "       [0.60587695, 0.34945028, 0.16437124,        inf]])"
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.fill_diagonal(dists, np.inf)\n",
    "dists"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1"
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.argmin(dists[0])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ok, so the closest city to City 0 is City 1. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can do this again with all the cities included, to get the closest overall."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Finding the distances to a query point\n",
    "\n",
    "We can also find the distances to a new \"test\" or \"query\" city:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 84.17901524],\n",
       "       [ 84.30562848],\n",
       "       [ 84.28113295],\n",
       "       [ 84.4453635 ],\n",
       "       [ 85.92913939],\n",
       "       [ 84.32289429],\n",
       "       [ 86.10623378],\n",
       "       [ 86.36094249],\n",
       "       [ 87.25286283],\n",
       "       [101.30925538]])"
      ]
     },
     "execution_count": 24,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "dists = euclidean_distances(cities_X, [[0, 0]])\n",
    "dists[:10]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "357"
      ]
     },
     "execution_count": 25,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.argmin(dists)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Apparently City 357 is closest to the point $(0,0)$."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Using sklearn `NearestNeighbours`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.neighbors import NearestNeighbors"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [],
   "source": [
    "nn = NearestNeighbors(n_neighbors=1)\n",
    "nn.fit(cities_X);"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(array([[81.81189213]]), array([[357]]))"
      ]
     },
     "execution_count": 28,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "nn.kneighbors([[0,0]])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "There it is again: City 357, with a distance of around 82."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can also find the closest city in the training set to city 0:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(array([[0.]]), array([[0]]))"
      ]
     },
     "execution_count": 29,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "nn.kneighbors(cities_X.iloc[[0]])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- Here we get the same problem as before, that the closest is itself.\n",
    "- Easiest hack to ignore this: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [],
   "source": [
    "nn = NearestNeighbors(n_neighbors=2)\n",
    "nn.fit(cities_X);"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [],
   "source": [
    "n_distances, n_inds = nn.kneighbors(cities_X.iloc[[0]])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[0.        , 0.25726537]])"
      ]
     },
     "execution_count": 37,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "n_distances"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[0, 1]])"
      ]
     },
     "execution_count": 35,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "n_inds"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So the closest is City 1."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Beyond 2 dimensions\n",
    "\n",
    "- All this matches our intuition of \"distance\" in the real world.\n",
    "- And we could also extend it to points in 3D space.\n",
    "- In fact, we can extend it to arrays (\"vectors\") of any length.\n",
    "- Here is the housing data again (I know I shouldn't be copy/pasting so many times...)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {},
   "outputs": [],
   "source": [
    "df = pd.read_csv(\"data/housing.csv\", index_col=0)\n",
    "\n",
    "df_train, df_test = train_test_split(df, random_state=123)\n",
    "\n",
    "X_train = df_train.drop(columns=['SalePrice'])\n",
    "y_train = df_train['SalePrice']\n",
    "\n",
    "X_test = df_test.drop(columns=['SalePrice'])\n",
    "y_test = df_test['SalePrice']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {},
   "outputs": [],
   "source": [
    "numeric_features     = ['LotFrontage', 'LotArea', 'OverallQual', 'OverallCond', 'YearBuilt', \n",
    "                        'YearRemodAdd', 'MasVnrArea', 'BsmtFinSF1', 'BsmtFinSF2', 'BsmtUnfSF', \n",
    "                        'TotalBsmtSF', '1stFlrSF', '2ndFlrSF', 'LowQualFinSF', 'GrLivArea', \n",
    "                        'BsmtFullBath', 'BsmtHalfBath', 'FullBath', 'HalfBath', \n",
    "                        'TotRmsAbvGrd', 'Fireplaces', 'GarageYrBlt', 'GarageCars', \n",
    "                        'GarageArea', 'WoodDeckSF', 'OpenPorchSF', 'EnclosedPorch', '3SsnPorch', \n",
    "                        'ScreenPorch', 'PoolArea', 'MiscVal', 'YrSold']\n",
    "ordinal_features_reg = ['ExterQual', 'ExterCond', 'BsmtQual', 'BsmtCond', 'HeatingQC', \n",
    "                        'KitchenQual', 'FireplaceQu', 'GarageQual', 'GarageCond', 'PoolQC']\n",
    "ordinal_features_oth = ['BsmtExposure', 'BsmtFinType1', 'BsmtFinType2', \n",
    "                        'Functional',  'Fence']\n",
    "categorical_features = list(set(X_train.columns) - set(numeric_features) - set(ordinal_features_reg))\n",
    "\n",
    "ordering = ['Po', 'Fa', 'TA', 'Gd', 'Ex']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {},
   "outputs": [],
   "source": [
    "numeric_preprocessing = make_pipeline(SimpleImputer(strategy='median'), \n",
    "                                      StandardScaler())\n",
    "ordinal_preprocessing = make_pipeline(SimpleImputer(strategy='most_frequent'), \n",
    "                                      OrdinalEncoder(categories=[ordering]*len(ordinal_features_reg)))\n",
    "categorical_preprocessing = make_pipeline(SimpleImputer(strategy='constant', fill_value=\"?\"),\n",
    "                                          OneHotEncoder(handle_unknown='ignore', sparse=False))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "metadata": {},
   "outputs": [],
   "source": [
    "preprocessing = ColumnTransformer([\n",
    "    ('numeric', numeric_preprocessing, numeric_features),\n",
    "    ('ordinal', ordinal_preprocessing, ordinal_features_reg),\n",
    "    ('categorical', categorical_preprocessing, categorical_features)\n",
    "])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "metadata": {},
   "outputs": [],
   "source": [
    "preprocessing.fit(X_train);"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "metadata": {},
   "outputs": [],
   "source": [
    "ohe_columns = list(preprocessing.named_transformers_['categorical'].named_steps['onehotencoder'].get_feature_names(categorical_features))\n",
    "new_columns = numeric_features + ordinal_features_reg + ohe_columns"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>LotFrontage</th>\n",
       "      <th>LotArea</th>\n",
       "      <th>OverallQual</th>\n",
       "      <th>OverallCond</th>\n",
       "      <th>YearBuilt</th>\n",
       "      <th>YearRemodAdd</th>\n",
       "      <th>MasVnrArea</th>\n",
       "      <th>BsmtFinSF1</th>\n",
       "      <th>BsmtFinSF2</th>\n",
       "      <th>BsmtUnfSF</th>\n",
       "      <th>...</th>\n",
       "      <th>Neighborhood_StoneBr</th>\n",
       "      <th>Neighborhood_Timber</th>\n",
       "      <th>Neighborhood_Veenker</th>\n",
       "      <th>RoofMatl_ClyTile</th>\n",
       "      <th>RoofMatl_CompShg</th>\n",
       "      <th>RoofMatl_Membran</th>\n",
       "      <th>RoofMatl_Roll</th>\n",
       "      <th>RoofMatl_Tar&amp;Grv</th>\n",
       "      <th>RoofMatl_WdShake</th>\n",
       "      <th>RoofMatl_WdShngl</th>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Id</th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>1447</th>\n",
       "      <td>-0.046315</td>\n",
       "      <td>1.654400</td>\n",
       "      <td>-0.775646</td>\n",
       "      <td>1.255836</td>\n",
       "      <td>-0.282035</td>\n",
       "      <td>-1.105566</td>\n",
       "      <td>0.491436</td>\n",
       "      <td>0.327430</td>\n",
       "      <td>-0.28498</td>\n",
       "      <td>0.064219</td>\n",
       "      <td>...</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1124</th>\n",
       "      <td>-0.888437</td>\n",
       "      <td>-0.093394</td>\n",
       "      <td>-0.775646</td>\n",
       "      <td>3.019968</td>\n",
       "      <td>-0.773017</td>\n",
       "      <td>1.117128</td>\n",
       "      <td>-0.569906</td>\n",
       "      <td>-0.942714</td>\n",
       "      <td>-0.28498</td>\n",
       "      <td>0.297501</td>\n",
       "      <td>...</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>187</th>\n",
       "      <td>-0.046315</td>\n",
       "      <td>-0.036795</td>\n",
       "      <td>0.647021</td>\n",
       "      <td>-0.508295</td>\n",
       "      <td>0.634466</td>\n",
       "      <td>0.295697</td>\n",
       "      <td>-0.569906</td>\n",
       "      <td>0.365985</td>\n",
       "      <td>-0.28498</td>\n",
       "      <td>0.023451</td>\n",
       "      <td>...</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1021</th>\n",
       "      <td>-0.420591</td>\n",
       "      <td>-0.342035</td>\n",
       "      <td>-1.486980</td>\n",
       "      <td>-0.508295</td>\n",
       "      <td>1.125448</td>\n",
       "      <td>0.972169</td>\n",
       "      <td>-0.569906</td>\n",
       "      <td>1.250588</td>\n",
       "      <td>-0.28498</td>\n",
       "      <td>-1.038776</td>\n",
       "      <td>...</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>68</th>\n",
       "      <td>0.140824</td>\n",
       "      <td>0.038184</td>\n",
       "      <td>0.647021</td>\n",
       "      <td>-0.508295</td>\n",
       "      <td>1.059984</td>\n",
       "      <td>0.875531</td>\n",
       "      <td>0.367894</td>\n",
       "      <td>1.227027</td>\n",
       "      <td>-0.28498</td>\n",
       "      <td>-0.286837</td>\n",
       "      <td>...</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>1.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.0</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "<p>5 rows × 292 columns</p>\n",
       "</div>"
      ],
      "text/plain": [
       "      LotFrontage   LotArea  OverallQual  OverallCond  YearBuilt  \\\n",
       "Id                                                                 \n",
       "1447    -0.046315  1.654400    -0.775646     1.255836  -0.282035   \n",
       "1124    -0.888437 -0.093394    -0.775646     3.019968  -0.773017   \n",
       "187     -0.046315 -0.036795     0.647021    -0.508295   0.634466   \n",
       "1021    -0.420591 -0.342035    -1.486980    -0.508295   1.125448   \n",
       "68       0.140824  0.038184     0.647021    -0.508295   1.059984   \n",
       "\n",
       "      YearRemodAdd  MasVnrArea  BsmtFinSF1  BsmtFinSF2  BsmtUnfSF  ...  \\\n",
       "Id                                                                 ...   \n",
       "1447     -1.105566    0.491436    0.327430    -0.28498   0.064219  ...   \n",
       "1124      1.117128   -0.569906   -0.942714    -0.28498   0.297501  ...   \n",
       "187       0.295697   -0.569906    0.365985    -0.28498   0.023451  ...   \n",
       "1021      0.972169   -0.569906    1.250588    -0.28498  -1.038776  ...   \n",
       "68        0.875531    0.367894    1.227027    -0.28498  -0.286837  ...   \n",
       "\n",
       "      Neighborhood_StoneBr  Neighborhood_Timber  Neighborhood_Veenker  \\\n",
       "Id                                                                      \n",
       "1447                   0.0                  0.0                   0.0   \n",
       "1124                   0.0                  0.0                   0.0   \n",
       "187                    0.0                  0.0                   0.0   \n",
       "1021                   0.0                  0.0                   0.0   \n",
       "68                     0.0                  0.0                   0.0   \n",
       "\n",
       "      RoofMatl_ClyTile  RoofMatl_CompShg  RoofMatl_Membran  RoofMatl_Roll  \\\n",
       "Id                                                                          \n",
       "1447               0.0               1.0               0.0            0.0   \n",
       "1124               0.0               1.0               0.0            0.0   \n",
       "187                0.0               1.0               0.0            0.0   \n",
       "1021               0.0               1.0               0.0            0.0   \n",
       "68                 0.0               1.0               0.0            0.0   \n",
       "\n",
       "      RoofMatl_Tar&Grv  RoofMatl_WdShake  RoofMatl_WdShngl  \n",
       "Id                                                          \n",
       "1447               0.0               0.0               0.0  \n",
       "1124               0.0               0.0               0.0  \n",
       "187                0.0               0.0               0.0  \n",
       "1021               0.0               0.0               0.0  \n",
       "68                 0.0               0.0               0.0  \n",
       "\n",
       "[5 rows x 292 columns]"
      ]
     },
     "execution_count": 45,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "X_train_enc = pd.DataFrame(preprocessing.transform(X_train), index=X_train.index, columns=new_columns)\n",
    "X_test_enc = pd.DataFrame(preprocessing.transform(X_test), index=X_test.index, columns=new_columns)\n",
    "\n",
    "X_train_enc.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- Each house is now a length-292 vectors (292 columns).\n",
    "- We could compute the distance between them."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 0.        ,  9.38944568,  6.92285556],\n",
       "       [ 9.38944568,  0.        , 10.42007981],\n",
       "       [ 6.92285556, 10.42007981,  0.        ]])"
      ]
     },
     "execution_count": 46,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "dists = euclidean_distances(X_train_enc[:3])\n",
    "dists"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "That is, the distance between house 1 and house 2 is:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "10.420079813360319"
      ]
     },
     "execution_count": 47,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "dists[1,2]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Find the most similar training house to test house 1:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "metadata": {},
   "outputs": [],
   "source": [
    "nn = NearestNeighbors(n_neighbors=1)\n",
    "nn.fit(X_train_enc);"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(array([[6.48464496]]), array([[135]]))"
      ]
     },
     "execution_count": 49,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "nn.kneighbors(X_test_enc.iloc[[1]])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can also ask `NearestNeighbors` to return, say, the 5 nearest neighbours:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Find the 5 most similar training houses to test house 1:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "metadata": {},
   "outputs": [],
   "source": [
    "nn = NearestNeighbors(n_neighbors=5)\n",
    "nn.fit(X_train_enc);"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "metadata": {
    "tags": [
     "raises-exception"
    ]
   },
   "outputs": [
    {
     "ename": "ValueError",
     "evalue": "Expected 2D array, got 1D array instead:\narray=[-0.42059121 -0.07303068 -1.4869801  -3.1544927  -2.31142832 -1.68539965\n -0.56990559 -0.94271399 -0.28497976  1.19665728  0.09078342 -0.1712858\n  0.75226225 -0.12494295  0.4713169   1.12185796 -0.23230498  0.78133697\n -0.76298931  0.89254471 -0.95298286 -2.38964366  1.66822305  1.47422693\n -0.75391691 -0.68595031  1.13785531 -0.10896508 -0.26487686 -0.07333341\n -0.10879978 -1.39396214  2.          2.          2.          1.\n  1.          2.          3.          1.          1.          4.\n  0.          0.          0.          0.          1.          0.\n  0.          0.          0.          1.          0.          0.\n  0.          0.          0.          0.          0.          0.\n  0.          0.          0.          1.          0.          0.\n  0.          0.          0.          0.          0.          0.\n  0.          1.          0.          0.          0.          0.\n  0.          0.          0.          0.          0.          0.\n  0.          0.          1.          0.          0.          0.\n  1.          0.          0.          0.          0.          0.\n  1.          0.          0.          0.          0.          1.\n  0.          0.          0.          0.          1.          0.\n  0.          0.          0.          0.          0.          1.\n  1.          0.          0.          0.          1.          0.\n  0.          1.          0.          0.          0.          0.\n  0.          0.          0.          0.          0.          0.\n  1.          0.          0.          0.          0.          0.\n  0.          1.          1.          0.          0.          0.\n  0.          0.          0.          0.          1.          0.\n  0.          1.          0.          0.          0.          0.\n  1.          0.          1.          0.          0.          0.\n  0.          1.          0.          0.          0.          0.\n  0.          0.          0.          1.          0.          0.\n  0.          0.          0.          0.          0.          1.\n  1.          0.          0.          0.          0.          0.\n  0.          0.          0.          0.          0.          1.\n  0.          0.          0.          0.          0.          0.\n  1.          0.          0.          0.          0.          0.\n  0.          0.          0.          1.          0.          0.\n  0.          0.          1.          0.          0.          1.\n  0.          0.          0.          0.          0.          1.\n  0.          0.          0.          0.          0.          0.\n  0.          0.          0.          0.          0.          0.\n  0.          0.          0.          0.          1.          0.\n  0.          0.          0.          0.          1.          1.\n  0.          0.          0.          0.          0.          1.\n  0.          0.          0.          0.          0.          1.\n  0.          0.          0.          0.          0.          0.\n  0.          0.          0.          0.          0.          0.\n  0.          0.          0.          0.          0.          0.\n  0.          1.          0.          0.          0.          0.\n  0.          0.          0.          0.          1.          0.\n  0.          0.          0.          0.        ].\nReshape your data either using array.reshape(-1, 1) if your data has a single feature or array.reshape(1, -1) if it contains a single sample.",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mValueError\u001b[0m                                Traceback (most recent call last)",
      "\u001b[0;32m<ipython-input-51-badcf1256763>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mnn\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mkneighbors\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mX_test_enc\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0miloc\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[0;32m/opt/miniconda3/envs/cpsc330env/lib/python3.8/site-packages/sklearn/neighbors/_base.py\u001b[0m in \u001b[0;36mkneighbors\u001b[0;34m(self, X, n_neighbors, return_distance)\u001b[0m\n\u001b[1;32m    604\u001b[0m                 \u001b[0mX\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0m_check_precomputed\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mX\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    605\u001b[0m             \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 606\u001b[0;31m                 \u001b[0mX\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mcheck_array\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mX\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0maccept_sparse\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m'csr'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m    607\u001b[0m         \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    608\u001b[0m             \u001b[0mquery_is_train\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mTrue\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
      "\u001b[0;32m/opt/miniconda3/envs/cpsc330env/lib/python3.8/site-packages/sklearn/utils/validation.py\u001b[0m in \u001b[0;36minner_f\u001b[0;34m(*args, **kwargs)\u001b[0m\n\u001b[1;32m     71\u001b[0m                           FutureWarning)\n\u001b[1;32m     72\u001b[0m         \u001b[0mkwargs\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mupdate\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m{\u001b[0m\u001b[0mk\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0marg\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mk\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0marg\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mzip\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0msig\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mparameters\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0margs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m}\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 73\u001b[0;31m         \u001b[0;32mreturn\u001b[0m \u001b[0mf\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m     74\u001b[0m     \u001b[0;32mreturn\u001b[0m \u001b[0minner_f\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     75\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
      "\u001b[0;32m/opt/miniconda3/envs/cpsc330env/lib/python3.8/site-packages/sklearn/utils/validation.py\u001b[0m in \u001b[0;36mcheck_array\u001b[0;34m(array, accept_sparse, accept_large_sparse, dtype, order, copy, force_all_finite, ensure_2d, allow_nd, ensure_min_samples, ensure_min_features, estimator)\u001b[0m\n\u001b[1;32m    618\u001b[0m             \u001b[0;31m# If input is 1D raise error\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    619\u001b[0m             \u001b[0;32mif\u001b[0m \u001b[0marray\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mndim\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 620\u001b[0;31m                 raise ValueError(\n\u001b[0m\u001b[1;32m    621\u001b[0m                     \u001b[0;34m\"Expected 2D array, got 1D array instead:\\narray={}.\\n\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    622\u001b[0m                     \u001b[0;34m\"Reshape your data either using array.reshape(-1, 1) if \"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
      "\u001b[0;31mValueError\u001b[0m: Expected 2D array, got 1D array instead:\narray=[-0.42059121 -0.07303068 -1.4869801  -3.1544927  -2.31142832 -1.68539965\n -0.56990559 -0.94271399 -0.28497976  1.19665728  0.09078342 -0.1712858\n  0.75226225 -0.12494295  0.4713169   1.12185796 -0.23230498  0.78133697\n -0.76298931  0.89254471 -0.95298286 -2.38964366  1.66822305  1.47422693\n -0.75391691 -0.68595031  1.13785531 -0.10896508 -0.26487686 -0.07333341\n -0.10879978 -1.39396214  2.          2.          2.          1.\n  1.          2.          3.          1.          1.          4.\n  0.          0.          0.          0.          1.          0.\n  0.          0.          0.          1.          0.          0.\n  0.          0.          0.          0.          0.          0.\n  0.          0.          0.          1.          0.          0.\n  0.          0.          0.          0.          0.          0.\n  0.          1.          0.          0.          0.          0.\n  0.          0.          0.          0.          0.          0.\n  0.          0.          1.          0.          0.          0.\n  1.          0.          0.          0.          0.          0.\n  1.          0.          0.          0.          0.          1.\n  0.          0.          0.          0.          1.          0.\n  0.          0.          0.          0.          0.          1.\n  1.          0.          0.          0.          1.          0.\n  0.          1.          0.          0.          0.          0.\n  0.          0.          0.          0.          0.          0.\n  1.          0.          0.          0.          0.          0.\n  0.          1.          1.          0.          0.          0.\n  0.          0.          0.          0.          1.          0.\n  0.          1.          0.          0.          0.          0.\n  1.          0.          1.          0.          0.          0.\n  0.          1.          0.          0.          0.          0.\n  0.          0.          0.          1.          0.          0.\n  0.          0.          0.          0.          0.          1.\n  1.          0.          0.          0.          0.          0.\n  0.          0.          0.          0.          0.          1.\n  0.          0.          0.          0.          0.          0.\n  1.          0.          0.          0.          0.          0.\n  0.          0.          0.          1.          0.          0.\n  0.          0.          1.          0.          0.          1.\n  0.          0.          0.          0.          0.          1.\n  0.          0.          0.          0.          0.          0.\n  0.          0.          0.          0.          0.          0.\n  0.          0.          0.          0.          1.          0.\n  0.          0.          0.          0.          1.          1.\n  0.          0.          0.          0.          0.          1.\n  0.          0.          0.          0.          0.          1.\n  0.          0.          0.          0.          0.          0.\n  0.          0.          0.          0.          0.          0.\n  0.          0.          0.          0.          0.          0.\n  0.          1.          0.          0.          0.          0.\n  0.          0.          0.          0.          1.          0.\n  0.          0.          0.          0.        ].\nReshape your data either using array.reshape(-1, 1) if your data has a single feature or array.reshape(1, -1) if it contains a single sample."
     ]
    }
   ],
   "source": [
    "nn.kneighbors(X_test_enc.iloc[1])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- You'll see this error a lot. \n",
    "- We need to pass in something 2D:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(292,)"
      ]
     },
     "execution_count": 55,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "X_test_enc.iloc[1].shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(1, 292)"
      ]
     },
     "execution_count": 53,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "X_test_enc.iloc[[1]].shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "nn.kneighbors(X_test_enc.iloc[[1]])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Cosine similarity (10 min)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- So far we've used Euclidean distance. \n",
    "- There are other useful alternatives."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "??NearestNeighbors"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- A particularly useful/popular is Cosine similarity.\n",
    "- This is the cosine of the angle between two vectors.\n",
    "  - If the angle is small, they are considered more similar.\n",
    "  - Zero angle means a cosine of 1 (most similar).\n",
    "  - If the angle is large, they are more different.\n",
    "  - 180 degree angle means a cosine of -1 (least similar).\n",
    "  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# TODO\n",
    "\n",
    "- would be great to have a vector diagram for the cities"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "x = np.linspace(0,180,1000)\n",
    "plt.plot(x,np.cos(x/180*np.pi));\n",
    "plt.xlabel(\"angle (degrees)\");\n",
    "plt.ylabel(\"cos(angle)\");"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- Because this is a similarity rather than a distance, everything is backwards.\n",
    "- We had the same thing with mean squared error vs. the $R^2$ score in regression, for example."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 57,
   "metadata": {},
   "outputs": [],
   "source": [
    "nn = NearestNeighbors(n_neighbors=5, metric='cosine')\n",
    "nn.fit(X_train_enc);"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 58,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(array([[0.        , 0.11861795, 0.13285294, 0.13464541, 0.15021607]]),\n",
       " array([[  1, 407, 174, 780, 242]]))"
      ]
     },
     "execution_count": 58,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "nn.kneighbors(X_train_enc.iloc[[1]])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- We can see above that the first training example has a score of 0 with itself.\n",
    "- sklearn is taking 1 minus the cosine similarity so that smaller values mean more similar, to be consistent with the distances.\n",
    "- Cosine similarity is between -1 and 1, sklearn cosine distance is between 0 and 2. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 59,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.metrics.pairwise import cosine_similarity, cosine_distances, euclidean_distances"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 60,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0.8813820462016351"
      ]
     },
     "execution_count": 60,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "cosine_similarity(X_train_enc)[1,407]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 61,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0.11861795379836493"
      ]
     },
     "execution_count": 61,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "cosine_distances(X_train_enc)[1,407]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 62,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "-0.1770598205351772"
      ]
     },
     "execution_count": 62,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "cosine_similarity(X_train_enc).min()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Euclidean vs. cosine distance\n",
    "\n",
    "- Cosine similarity relies on an origin, whereas Euclidean distance does not\n",
    "- If we preprocess our features, the origin would be like an \"average\" case sort of."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 63,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[1.        , 0.71287876, 0.79768377, 0.74636167, 0.75000074],\n",
       "       [0.71287876, 1.        , 0.64925291, 0.71424853, 0.6338147 ],\n",
       "       [0.79768377, 0.64925291, 1.        , 0.8503276 , 0.90210757],\n",
       "       [0.74636167, 0.71424853, 0.8503276 , 1.        , 0.88156876],\n",
       "       [0.75000074, 0.6338147 , 0.90210757, 0.88156876, 1.        ]])"
      ]
     },
     "execution_count": 63,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "cosine_similarity(X_train_enc)[:5,:5]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "is different from"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 64,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[1.        , 0.86866052, 0.91740762, 0.89392321, 0.89308973],\n",
       "       [0.86866052, 1.        , 0.83651963, 0.86502662, 0.82556117],\n",
       "       [0.91740762, 0.83651963, 1.        , 0.93437979, 0.95585103],\n",
       "       [0.89392321, 0.86502662, 0.93437979, 1.        , 0.94644209],\n",
       "       [0.89308973, 0.82556117, 0.95585103, 0.94644209, 1.        ]])"
      ]
     },
     "execution_count": 64,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "cosine_similarity(X_train_enc-1)[:5,:5]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "whereas"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 65,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 0.        ,  9.38944568,  6.92285556,  7.93960703,  7.86725903],\n",
       "       [ 9.38944568,  0.        , 10.42007981,  9.53671484, 10.74674242],\n",
       "       [ 6.92285556, 10.42007981,  0.        ,  6.22682236,  5.03191983],\n",
       "       [ 7.93960703,  9.53671484,  6.22682236,  0.        ,  5.62436894],\n",
       "       [ 7.86725903, 10.74674242,  5.03191983,  5.62436894,  0.        ]])"
      ]
     },
     "execution_count": 65,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "euclidean_distances(X_train_enc)[:5,:5]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "is the same as"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 66,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 0.        ,  9.38944568,  6.92285556,  7.93960703,  7.86725903],\n",
       "       [ 9.38944568,  0.        , 10.42007981,  9.53671484, 10.74674242],\n",
       "       [ 6.92285556, 10.42007981,  0.        ,  6.22682236,  5.03191983],\n",
       "       [ 7.93960703,  9.53671484,  6.22682236,  0.        ,  5.62436894],\n",
       "       [ 7.86725903, 10.74674242,  5.03191983,  5.62436894,  0.        ]])"
      ]
     },
     "execution_count": 66,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "euclidean_distances(X_train_enc-1)[:5,:5]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Thinking about 2D geometry"
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {},
   "source": [
    "Smaller angle\n",
    "\n",
    "|\\\n",
    "| \\\n",
    "|  \\\n",
    "|   \\"
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {},
   "source": [
    "Medium angle\n",
    "\n",
    "-------\n",
    "\\\n",
    " \\\n",
    "  \\ \n",
    "   \\"
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {},
   "source": [
    "Large angle\n",
    "\n",
    "    ________\n",
    "   /\n",
    "  /\n",
    " /\n",
    "/"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can bring back the cilantro dataset:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 67,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>meat</th>\n",
       "      <th>grade</th>\n",
       "      <th>cilantro</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>42.0</td>\n",
       "      <td>90</td>\n",
       "      <td>Yes</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>85.0</td>\n",
       "      <td>83</td>\n",
       "      <td>No</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>28.0</td>\n",
       "      <td>83</td>\n",
       "      <td>Yes</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>100.0</td>\n",
       "      <td>80</td>\n",
       "      <td>No</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>100.0</td>\n",
       "      <td>75</td>\n",
       "      <td>No</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "    meat  grade cilantro\n",
       "0   42.0     90      Yes\n",
       "1   85.0     83       No\n",
       "2   28.0     83      Yes\n",
       "3  100.0     80       No\n",
       "4  100.0     75       No"
      ]
     },
     "execution_count": 67,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "cilantro_df = pd.read_csv('data/330-students-cilantro.csv')\n",
    "cilantro_df.columns = [\"meat\", \"grade\", \"cilantro\"]\n",
    "cilantro_df.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 68,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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rKk0f3skrVixwppeuemmm4LSQyuvER0dLV27drV18LVr15Z169a5bZrF6dOnZeDAgdbGkN7PHtevX++2eZp0cMOpYZTpvPCR+azoALDc63g5YAFwFHjSCeMCkfKSIH388cfWl/29997za90nT56Ua665RgAJDw/Xv2hdxuPxyLx586RMmTI2R4BRo0YFzWLRc+fOyZtvvinFixe3CdFdd90lP//8s9vmaS6BG4J0DHjc631VIJlUa3ww1v/sdMK4QKS8IkgXL16UcuXKWavY/blw0OPxyEMPPWR1KJMnT/Zb3ZrMExkZKa1bt7Z18k2bNg2ajediY2PlnXfekSuvvNJm46233irff/+99sjMAbghSAexb09eB2Pb8jLplC3ghHGBSHlFkN5//33HBOOjjz6y6n744Yd1h+ISSUlJMn78eNuePxERETJz5syg+J8kJCTI5MmTbQtwAalVq5Z88803QWGjxjfcEKT+GGuMFmOsOYomne0ecnrKC4IUExNjheGvXLmyX6dstm3bZu2+ec011wT91tW5lc2bN0v9+vVtHX2HDh2CYp1OUlKSfPrpp9bGjCmpWrVq8vnnnwfduifN5XHLqaEp8DbwPtAeCHHCCDdTXhCkUaNGWZ3Ap59+6rd6L168KDfeeKPltRXs21fnRmJjY+XVV1+V0NBQ639csWLFoHC3T05Olvnz50vNmjVtQlS+fHmZNm2aJCYmum2iJosEhdt3bky5XZCio6OlRIkSAsaeMP4Mw//CCy9YncyAAQP8Vq/GN1asWGF5TQISEhIi/fv3l5iYGFft8ng8smTJEqlTp45NiEqXLi3jx4/X3pe5AC1IWpCyxGuvvWZ1CAsWLPBbvUuWLLHqrVevnsTHx/utbs2liYqKko4dO9o6+zp16ji69byv/PTTT2miQJQsWVLGjBnjulBq/IcWJC1ImebEiRPWA+66dev67aHxsWPHrGdShQsXDhrvrdyOx+OR2bNn27zTChUqJOPGjXN9+uv333+Xe++91yZERYoUkSFDhsiZM2dctU3jf7QgaUHKNP369bM6h++++84vdSYnJ0vLli2temfOnOmXejWXZu/evWkiXbdo0UL27t3rql2bN2+2ufwDUrBgQenfv7+cOHHCVds0zqEFSQtSpjh06JAVgqVhw4Z+Gx299957Vsfz+OOPa1ddh0lMTJSxY8dK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Inv27JGiRYtajgVHjhzxub6M8Hg8MnPmTImIiLA5IowfP95az3T69GkZNGiQFC5c2CZELVu29Pt0oUaj0XijBSkIBCllm+58+fLJv//+KwkJCdKgQQNLDBYtWuRzXRmxc+dOadKkiU1kWrduba1lOn/+vIwYMcJa55SS7rrrLlmzZk2229doNJrLoQXJZUH67bffrM7/2WefFRGR1157zcrr2bOnT/VkREJCgowcOdI29Va6dGmZN2+eeDweuXjxorz77ru2BbCA1KtXT77//nsdb06j0QQMLUguC1LKqCV//vyyf/9++emnnyxPthtuuCFbUQ7WrVsntWvXtglN165d5fTp05KQkCCTJ0+WsmXL2o7XqlVLvv76ay1EGo0m4GhBclGQVqxYYQlBr1695PTp01KuXDlrbU9Wd009d+6c9O7d2+aiXb16dVm9erUkJSXJp59+KpUrV7YJUdWqVWXOnDk68KlGo3ENLUguCZLH47GeE4WHh8uRI0ekbdu2lkBMnDjxkudnxMKFCy1RSxl5DRkyRC5evCgLFiyQ66+/3iZE5cuXl6lTp+rApxqNxnWcEqRQNJdk8eLF/P777wD07t2b7777jgULFgDQqlUrevbsman6jh49Sp8+faw6ABo2bMiUKVOIjIykYcOGbNy40Tp21VVXMXjwYLp160bBggX9cEUajUYTnGhBugQej4fXXnsNgGLFivHII4/QrFkzAMqUKcPMmTNRSvlc19SpUxkwYABnz5616nzrrbeoXr063bt359dff7XKlyxZkldeeYXevXtTuHBhP1+ZRqPRBB9akC7B/Pnz+fvvvwHo06cP//d//0dsbCwAn3zyCaVKlfKpnn/++Ydu3brxyy+/WHmPPvoozz33HO+//z7Lly+38osUKULfvn3p168fJUqU8N/FaDQaTZCjBSkDkpKSGDJkCABXXHEFZ8+e5a+//gKgX79+NG/e/LJ1xMfHM2bMGEaNGkVCQgIAZcuW5eWXX+bHH3+kVatWVtmwsDB69erFgAEDfBY6jUajyU1oQcqAzz77jJ07dwLGaGbixIkA3HzzzYwaNeqy5//8889069aNHTt2AKCU4sknnyQuLo6+ffsaHiVAaGgoXbp04bXXXqNs2bIOXY1Go9EEP1qQ0iE+Pp5hw4YBhlPBt99+C0B4eDhz584lLCwsw3PPnDnDgAED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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "n = 4\n",
    "plt.quiver(np.zeros(n), np.zeros(n), cilantro_df[\"meat\"][:n], cilantro_df[\"grade\"][:n], angles='xy', scale_units='xy', scale=1);\n",
    "plt.xlim([0,100]);\n",
    "plt.ylim([0,100]);\n",
    "plt.xlabel(\"% of days eating meat\");\n",
    "plt.ylabel(\"expected % grade\");"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- From here we can see that more similar students will have a smaller angle between them.\n",
    "- In this case the angle cannot be more than 90 degrees so the cosine similarity is between 0 and 1.\n",
    "- In general here, pairs of students with small Euclidean distance will also have a small Euclidean distance.\n",
    "- We'll see some deviations from this later today."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Break (5 min)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "REMINDER TO RESUME RECORDING!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<br><br>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Amazon product dataset (5 min)\n",
    "\n",
    "We'll be looking at the [Amazon product data set](http://jmcauley.ucsd.edu/data/amazon/). The author of the data set has asked for the following citations:\n",
    "\n",
    "> Ups and downs: Modeling the visual evolution of fashion trends with one-class collaborative filtering.\n",
    "> R. He, J. McAuley.\n",
    "> WWW, 2016.\n",
    "> \n",
    "> Image-based recommendations on styles and substitutes.\n",
    "> J. McAuley, C. Targett, J. Shi, A. van den Hengel.\n",
    "> SIGIR, 2015.\n",
    "\n",
    "We will focus on the Patio, Lawn, and Garden section. Please download the [ratings](http://snap.stanford.edu/data/amazon/productGraph/categoryFiles/ratings_Patio_Lawn_and_Garden.csv) and place them in the data directory with their default filenames. The code below should load the data:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 69,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>user</th>\n",
       "      <th>item</th>\n",
       "      <th>rating</th>\n",
       "      <th>timestamp</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>A2VNYWOPJ13AFP</td>\n",
       "      <td>0981850006</td>\n",
       "      <td>5.0</td>\n",
       "      <td>1259798400</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>A20DWVV8HML3AW</td>\n",
       "      <td>0981850006</td>\n",
       "      <td>5.0</td>\n",
       "      <td>1371081600</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>A3RVP3YBYYOPRH</td>\n",
       "      <td>0981850006</td>\n",
       "      <td>5.0</td>\n",
       "      <td>1257984000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>A28XY55TP3Q90O</td>\n",
       "      <td>0981850006</td>\n",
       "      <td>5.0</td>\n",
       "      <td>1314144000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>A3VZW1BGUQO0V3</td>\n",
       "      <td>0981850006</td>\n",
       "      <td>5.0</td>\n",
       "      <td>1308268800</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "             user        item  rating   timestamp\n",
       "0  A2VNYWOPJ13AFP  0981850006     5.0  1259798400\n",
       "1  A20DWVV8HML3AW  0981850006     5.0  1371081600\n",
       "2  A3RVP3YBYYOPRH  0981850006     5.0  1257984000\n",
       "3  A28XY55TP3Q90O  0981850006     5.0  1314144000\n",
       "4  A3VZW1BGUQO0V3  0981850006     5.0  1308268800"
      ]
     },
     "execution_count": 69,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "ratings = pd.read_csv(\"data/ratings_Patio_Lawn_and_Garden.csv\", names=(\"user\",\"item\",\"rating\",\"timestamp\"))\n",
    "ratings.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- Our goal will be to find similar items to recommend given an item. \n",
    "- We will do this by recommending items that got similar ratings from the same users.\n",
    "- Another approach would be to look at features of the items like name, price, etc.\n",
    "- The methods we'll talk about today would apply equally well there."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's compute some summary statistics:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 70,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "993490"
      ]
     },
     "execution_count": 70,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "len(ratings)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 71,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "4.006400668350965"
      ]
     },
     "execution_count": 71,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.mean(ratings[\"rating\"])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Histogram of number of ratings per item:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 72,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "ratings['item'].value_counts().plot.hist(log=True);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Histogram of number of ratings per user:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 73,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "ratings['user'].value_counts().plot.hist(log=True);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- Note the log scales on the vertical axes. \n",
    "- We can see some \"super items\" and \"super reviewers\"."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Finding similar items\n",
    "\n",
    "- The plan: create a matrix of items x users.\n",
    "  - Use nearest neighbours to find similar items.\n",
    "  - **The users become the features**.\n",
    "- Problem: how big is this matrix going to be?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 74,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "105984"
      ]
     },
     "execution_count": 74,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "n_items = len(ratings[\"item\"].unique())\n",
    "n_items"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 75,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "714791"
      ]
     },
     "execution_count": 75,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "n_users = len(ratings[\"user\"].unique())\n",
    "n_users"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 76,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Size of full matrix: 606 GB\n"
     ]
    }
   ],
   "source": [
    "print(\"Size of full matrix: %.0f GB\" % ((n_items*n_users)*8/1e9))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![](img/mike_not_gonna_happen.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- Is there hope?\n",
    "- Well, if we were to create such a dataframe, it would be almost entirely zeros:\n",
    "- Note: we are going to represent missing data (user did not rate that item) as zero"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 77,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "993490"
      ]
     },
     "execution_count": 77,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "len(ratings)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 78,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "75756409344"
      ]
     },
     "execution_count": 78,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "n_items*n_users"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 79,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Fraction zeros: 0.9999868857300841\n"
     ]
    }
   ],
   "source": [
    "print(\"Fraction zeros:\", 1-len(ratings)/(n_items*n_users))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So, around 99.999% zeros."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Good news: there's a better way!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Sparse matrices in Python (15 min)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's take a detour back to word counts:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 80,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>type</th>\n",
       "      <th>review</th>\n",
       "      <th>label</th>\n",
       "      <th>file</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>12438</th>\n",
       "      <td>test</td>\n",
       "      <td>As Jennifer Denuccio used to say on Square Peg...</td>\n",
       "      <td>neg</td>\n",
       "      <td>9946_2.txt</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5705</th>\n",
       "      <td>test</td>\n",
       "      <td>With Knightly and O'Tool as the leads, this fi...</td>\n",
       "      <td>neg</td>\n",
       "      <td>3886_3.txt</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>11675</th>\n",
       "      <td>test</td>\n",
       "      <td>Take a bad script, some lousy acting and throw...</td>\n",
       "      <td>neg</td>\n",
       "      <td>9259_1.txt</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>9824</th>\n",
       "      <td>test</td>\n",
       "      <td>Strange things happen to Americans Will (Greg ...</td>\n",
       "      <td>neg</td>\n",
       "      <td>7593_3.txt</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>22581</th>\n",
       "      <td>test</td>\n",
       "      <td>Sometimes, you're up late at night flipping th...</td>\n",
       "      <td>pos</td>\n",
       "      <td>7824_7.txt</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "       type                                             review label  \\\n",
       "12438  test  As Jennifer Denuccio used to say on Square Peg...   neg   \n",
       "5705   test  With Knightly and O'Tool as the leads, this fi...   neg   \n",
       "11675  test  Take a bad script, some lousy acting and throw...   neg   \n",
       "9824   test  Strange things happen to Americans Will (Greg ...   neg   \n",
       "22581  test  Sometimes, you're up late at night flipping th...   pos   \n",
       "\n",
       "             file  \n",
       "12438  9946_2.txt  \n",
       "5705   3886_3.txt  \n",
       "11675  9259_1.txt  \n",
       "9824   7593_3.txt  \n",
       "22581  7824_7.txt  "
      ]
     },
     "execution_count": 80,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "imdb_df = pd.read_csv('data/imdb_master.csv', index_col=0, encoding=\"ISO-8859-1\")\n",
    "imdb_df = imdb_df[imdb_df['label'].str.startswith(('pos','neg'))]\n",
    "imdb_df = imdb_df.sample(frac=0.2, random_state=999) # Take a subsample of the dataset for speed\n",
    "imdb_df.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 81,
   "metadata": {},
   "outputs": [],
   "source": [
    "countvec = CountVectorizer()\n",
    "X_train_counts = countvec.fit_transform(imdb_df[\"review\"])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's look at the type of the output:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 82,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "scipy.sparse.csr.csr_matrix"
      ]
     },
     "execution_count": 82,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "type(X_train_counts)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 83,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<10000x52863 sparse matrix of type '<class 'numpy.int64'>'\n",
       "\twith 1377321 stored elements in Compressed Sparse Row format>"
      ]
     },
     "execution_count": 83,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "X_train_counts"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This is a **sparse matrix**. Why? Look at the shape:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 84,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(10000, 52863)"
      ]
     },
     "execution_count": 84,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "X_train_counts.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "How many elements total?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "np.prod(X_train_counts.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A lot! How many are nonzero though?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_train_counts.nnz"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 85,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0.00260545371999319"
      ]
     },
     "execution_count": 85,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "frac_nz = X_train_counts.nnz / np.prod(X_train_counts.shape)\n",
    "frac_nz"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- This happens because most words do not appear in a given document. \n",
    "- We get massive computational savings if we **only store the nonzero elements**. \n",
    "- There is a bit of overhead, because we also need to **store the locations**:\n",
    "  - e.g. \"location (5,192): 3\".\n",
    "  - However, if the fraction of nonzero is small, this is a huge win."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here are the nonzero elements in the first review:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(X_train_counts[0])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- BTW, have you noticed that with `OneHotEncoder` we've been setting `sparse=False`. \n",
    "- This is to get it to return a regular numpy array instead of a sparse array, so we didn't have to deal with these.\n",
    "- If there are a huge number of categories, it may be beneficial to keep them as sparse.\n",
    "- For smaller number of categories, it doesn't matter much."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Working with scipy.sparse matrices\n",
    "\n",
    "- We won't go into implementation details here, but there are some \"gotchas\" with `scipy.sparse`.\n",
    "- For example, with a regular numpy array, `x[i,j]` and `x[i][j]` are equivalent:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "x = np.random.rand(10, 10)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "x[1, 2]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "x[1][2]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "(x[1])[2]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This is because `x[1]` returns the first row, and then the `[2]` indexes into that row:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "x[1]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "row_1 = x[1]\n",
    "row_1[2]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "However, with `scipy.sparse` matrices, things are a bit different:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "x_sparse = csr_matrix(x)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "x_sparse[1, 2]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "raises-exception"
    ]
   },
   "outputs": [],
   "source": [
    "x_sparse[1][2]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Why?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "row_1_sparse = x_sparse[1]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "row_1_sparse.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- The sparse matrix returns a different shape, leaving in the first dimension.\n",
    "- This can be annoying and is something to watch out for.\n",
    "- In general, I suggest using the `x[1,2]` notation when possible because chaining the `[]` can be problematic in several places (e.g., also pandas).\n",
    "- However, this is only for numpy, not, say, a list of lists:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "lst = [[1, 2, 3], [4, 5, 6], [7, 9]]\n",
    "lst"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "lst[0][1]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "raises-exception"
    ]
   },
   "outputs": [],
   "source": [
    "lst[0, 1]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### sparse matrix operations"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's try to find the highest word count. But first, review the `axis` keyword:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "x = np.random.randint(10, size=(4,5))\n",
    "x"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "np.sum(x)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "np.sum(x, axis=0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "np.sum(x, axis=1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Back to our word counts:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_train_counts.max()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The max for each document:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_train_counts.max(axis=1).toarray()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The document with the highest word count:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "np.argmax(X_train_counts.max(axis=1))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- Regular numpy functions work on sparse matrices, although they might be fast/slow depending. \n",
    "- You definitely do not want to iterate with loops - make sure you use builtin functions. \n",
    "- There are some details here, in that a sparse matrix max be stored row-by-row or column-by-column, and this affects speed.\n",
    "  - This is beyond the scope of the course, but something to look into if your code is too slow."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Back to the ratings data\n",
    "\n",
    "- We had the same situation as with the word counts\n",
    "  - Large number of elements, mostly zero\n",
    "  - So we can make a scipy sparse matrix of the users x items and then do nearest neighbours!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 86,
   "metadata": {},
   "outputs": [],
   "source": [
    "user_mapper = {x : i for i, x in enumerate(ratings[\"user\"].unique())}\n",
    "item_mapper = {x : i for i, x in enumerate(ratings[\"item\"].unique())}\n",
    "\n",
    "user_inverse_mapper = {i : x for i, x in enumerate(ratings[\"user\"].unique())}\n",
    "item_inverse_mapper = {i : x for i, x in enumerate(ratings[\"item\"].unique())}\n",
    "\n",
    "user_ind = [user_mapper[i] for i in ratings[\"user\"]]\n",
    "item_ind = [item_mapper[i] for i in ratings[\"item\"]]\n",
    "\n",
    "X_user_item = csc_matrix((ratings[\"rating\"], (item_ind, user_ind)), shape=(n_items, n_users)).T"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 87,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "scipy.sparse.csr.csr_matrix"
      ]
     },
     "execution_count": 87,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "type(X_user_item)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 88,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(714791, 105984)"
      ]
     },
     "execution_count": 88,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "X_user_item.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 89,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "993490"
      ]
     },
     "execution_count": 89,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "X_user_item.nnz"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# TODO\n",
    "\n",
    "draw this table like we do in lecture 4 with the word counts"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### EDA\n",
    "\n",
    "Let's find the item with the most reviews:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 90,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "10959"
      ]
     },
     "execution_count": 90,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "idx_most_reviews = np.argmax(np.sum(X_user_item>0, axis=0))\n",
    "idx_most_reviews"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 91,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "3180"
      ]
     },
     "execution_count": 91,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.max(np.sum(X_user_item>0, axis=0))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Then we can look up the Amazon id:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 92,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'B000HCLLMM'"
      ]
     },
     "execution_count": 92,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "item_inverse_mapper[idx_most_reviews]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And get the URL:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 93,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<a href=\"https://www.amazon.com/dp/B000HCLLMM\">https://www.amazon.com/dp/B000HCLLMM</a>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "url_amazon = \"https://www.amazon.com/dp/%s\"\n",
    "def disp_url(item_id):\n",
    "    url = url_amazon % item_id\n",
    "    display(HTML('<a href=\"%s\">%s</a>' % (url,url)))\n",
    "\n",
    "disp_url(item_inverse_mapper[idx_most_reviews])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Nearest neighbours for product similarity (5 min)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's find find the 5 items most similar to [GRILL HOGS 18\" GRILL CLEANING BRUSH](https://www.amazon.com/dp/B00CFM0P7Y). "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 94,
   "metadata": {},
   "outputs": [],
   "source": [
    "grill_brush = \"B00CFM0P7Y\"\n",
    "grill_brush_ind = item_mapper[grill_brush]\n",
    "grill_brush_vec = X_user_item[grill_brush_ind]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 102,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[0., 0., 0., ..., 0., 0., 0.]])"
      ]
     },
     "execution_count": 102,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "grill_brush_vec.toarray()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here is the strategy:\n",
    "\n",
    "- We have a matrix `X_user_item` of users x items.\n",
    "- Let's transpose it:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 95,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_item_user = X_user_item.T"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 96,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(714791, 105984)"
      ]
     },
     "execution_count": 96,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "X_user_item.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 97,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(105984, 714791)"
      ]
     },
     "execution_count": 97,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "X_item_user.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 99,
   "metadata": {},
   "outputs": [],
   "source": [
    "# type(X_item_user)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 100,
   "metadata": {},
   "outputs": [],
   "source": [
    "# type(X_user_item)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- This is now items x users.\n",
    "- Each item is an observation, and we treat the users as features/columns.\n",
    "- Assumption: **a similar item is an item that receives similar reviews by the same people.**\n",
    "- In that case, we can just use nearest neighbours!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 103,
   "metadata": {},
   "outputs": [],
   "source": [
    "nn = NearestNeighbors(n_neighbors=6)\n",
    "nn.fit(X_item_user);"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 104,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 93652, 103866, 103865,  98897,  72226, 102810]])"
      ]
     },
     "execution_count": 104,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "distances, nearby_items = nn.kneighbors(X_item_user[grill_brush_ind])\n",
    "nearby_items"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The first one is the grill brush itself:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 105,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "93652"
      ]
     },
     "execution_count": 105,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "grill_brush_ind"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So the neighbours are:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 106,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([103866, 103865,  98897,  72226, 102810])"
      ]
     },
     "execution_count": 106,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "nearby_items = np.squeeze(nearby_items)[1:]\n",
    "nearby_items"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 107,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<a href=\"https://www.amazon.com/dp/B00IJB5MCS\">https://www.amazon.com/dp/B00IJB5MCS</a>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<a href=\"https://www.amazon.com/dp/B00IJB4MLA\">https://www.amazon.com/dp/B00IJB4MLA</a>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<a href=\"https://www.amazon.com/dp/B00EXE4O42\">https://www.amazon.com/dp/B00EXE4O42</a>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<a href=\"https://www.amazon.com/dp/B00743MZCM\">https://www.amazon.com/dp/B00743MZCM</a>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<a href=\"https://www.amazon.com/dp/B00HVXQY9A\">https://www.amazon.com/dp/B00HVXQY9A</a>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "for item in nearby_items:\n",
    "    disp_url(item_inverse_mapper[item])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 108,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 0.        , 74.24284477, 75.14652354, 76.51797175, 76.51797175,\n",
       "        76.51797175]])"
      ]
     },
     "execution_count": 108,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "distances"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "How many reviews does the grill brush have?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 109,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "272"
      ]
     },
     "execution_count": 109,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "(X_item_user[grill_brush_ind] > 0).sum()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "How many reviewers do they have in common?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 110,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "37\n",
      "28\n",
      "1\n",
      "1\n",
      "1\n"
     ]
    }
   ],
   "source": [
    "for item in nearby_items:\n",
    "    print(np.sum(np.squeeze(X_item_user[grill_brush_ind].toarray()) * np.squeeze(X_item_user[item].toarray()) >0))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Distances with sparse data (15 min)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- Is Euclidean distance the best choice?\n",
    "- Are similar items near each other?\n",
    "- Let's say there are 2 users, Alice and Bob."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 111,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>Alice</th>\n",
       "      <th>Bob</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>Query</th>\n",
       "      <td>5</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Item 1</th>\n",
       "      <td>1</td>\n",
       "      <td>2</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Item 2</th>\n",
       "      <td>5</td>\n",
       "      <td>5</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "        Alice  Bob\n",
       "Query       5    0\n",
       "Item 1      1    2\n",
       "Item 2      5    5"
      ]
     },
     "execution_count": 111,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "df = pd.DataFrame(data=[[5, 0], [1, 2], [5, 5]], columns=[\"Alice\", \"Bob\"], index=[\"Query\", \"Item 1\", \"Item 2\"])\n",
    "df"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Which item do you consider more similar to the Query item, Item 1 or Item 2?\n",
    "\n",
    "<br><br><br><br><br><br>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- I would say Item 2, because Alice liked both Item 2 and Query.\n",
    "- Let's look at them as vectors:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 112,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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aBEA9913H3369PGtO3bsGN26daNZs2a+BFC/fn3i4uIYNmyYJQCTq3322Wfcc889lCxZkpCQEKpXr07//v1997CYvCO9JJAP516AJEmPNdV2qZ8b4PPPP2fQoEEAXH311UyfPt13Q9enn35KrVq1mDhxIgAhISGMHj2aNWvWULt27YDFbIwXI0aMICoqipCQEKZOncrixYvp0qUL06ZN46abbuLXX38NdIgmM9K6gQDnhrD7gSruco1b1jJZWRWgDZCQVj1ZueSVm8V+/vlnLVWqlAIaEhKiGzZsUFXVgwcPart27VLc9NWkSRP98ccfAxyxMd4sXbpURUSfeeaZ89bt3LlTw8PD9c4778yxeBITE/XMmTM5dry8inRuFssoCSSkWtIsS6uerFzyQhI4c+aMNmzY0PchP23aNE1MTNRZs2Zp6dKlfeVFixbVCRMmaEJCQqBDNsazu+++W0uWLKmnTp3yu/6VV15RQOPi4nTXrl2+/4Hkli1bpoAuW7YsRfncuXO1QYMGGhoaqsWLF9e2bdvqnj17UmxTqVIlbdeuncbGxmr16tW1QIECOmvWLC1VqpTfxDRt2jQFdNu2bRf1uvO69JJAeg3DHS76NCMIPffcc6xZswaAjh07cuedd3LffffxySef+La59957mThxIldddVWgwjQm0+Lj41mxYgWtW7dOc5KiVq1a0a9fP7744gseeughz3VPnDiRbt260aFDBwYNGsTx48cZMmQITZs25fvvv08xJeqyZcvYsGEDgwcP5oorriAiIoIOHTowdepURo4cmSK2SZMm0bRpU2rUqHHhL/xSl1Z2yI1Lbj8TmDVrlu+bfp06dTQmJkaLFSvmKytZsqTOmDFDExMTAx2qMZm2f/9+BbR///5pbnPq1CkFtHv37p7PBI4fP67FihXTDh06pNhu165dWrBgQX3jjTd8ZZUqVdLQ0FDdt29fim137typ+fLl0/fee89XtnHjRgX0gw8+uLAXfAkhnTOBHB93WESuEpE5InJURI6JyDwRqZjTcWS1H374gY4dOwJQtGhRQkJC6NGjB8eOHQPg73//O1u3bqVdu3Y24qfJk5zPEm8yM6T56tWrOXbsGO3atSM+Pt63VKhQgRo1arBy5coU2zds2PC8e20qV65MVFQUkyZN8pVNmjSJ0qVL88ADNqhBejI9dtDFcAefWwqc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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.quiver(np.zeros(3), np.zeros(3), df[\"Alice\"], df[\"Bob\"], angles='xy', scale_units='xy', scale=1);\n",
    "plt.xlim([-1, 8]);\n",
    "plt.ylim([-1, 8]);\n",
    "plt.xticks([0,1,2,3,4,5])\n",
    "plt.yticks([0,1,2,3,4,5])\n",
    "for ind, val in df.iterrows():\n",
    "    plt.text(val[\"Alice\"], val[\"Bob\"], ind);\n",
    "plt.xlabel(\"Alice's rating (stars)\");\n",
    "plt.ylabel(\"Bob's rating (stars)\");\n",
    "# plt.axis('equal');"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Question: which has a smaller distance to the Query item, Item 1 or Item 2?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 113,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>Query</th>\n",
       "      <th>Item 1</th>\n",
       "      <th>Item 2</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>Query</th>\n",
       "      <td>0.000000</td>\n",
       "      <td>4.472136</td>\n",
       "      <td>5.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Item 1</th>\n",
       "      <td>4.472136</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>5.0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Item 2</th>\n",
       "      <td>5.000000</td>\n",
       "      <td>5.000000</td>\n",
       "      <td>0.0</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "           Query    Item 1  Item 2\n",
       "Query   0.000000  4.472136     5.0\n",
       "Item 1  4.472136  0.000000     5.0\n",
       "Item 2  5.000000  5.000000     0.0"
      ]
     },
     "execution_count": 113,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "dists_df = pd.DataFrame(data=euclidean_distances(df), columns=df.index, index=df.index)\n",
    "dists_df"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- Item 1, apparently.\n",
    "- However, if we look at the **angle** instead of the distance, then we get Item 2:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![](img/cosine_sim.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- So we might want to use **cosine similarity**, which looks at the angle instead of the distance.\n",
    "- But furthermore, what is really the goal here, just similarity?\n",
    "- Wouldn't it make sense to typically recommend items that are more popular in general?\n",
    "- Even if we weren't sure whether Item 1 or Item 2 was more similar, Item 2 is more popular, so let's recommend that?\n",
    "- It turns out cosine similarity helps with that as well.\n",
    "  - Why? It has to do with high-dimensional intuition again.\n",
    "  - Combined with the fact that these vectors are actually quite sparse.\n",
    "  - We won't go into detail here."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### (From last year's Piazza - to read over later) Notes on Cosine similarity vs Euclidean distance\n",
    "\n",
    "Q1: Why is a small angle good?\n",
    "\n",
    "Cosine similarity tries to minimize the angle. An identical product would have an angle of zero, so that would be considered the most similar, which is good. Let's assume for a minute that the ratings were from -5 stars to 5 stars. Then, if you had two products A and B with exact opposite reviews (if I gave product A a 4 stars, then I gave product B -4 stars), those vectors would be pointing in exact opposite directions and have an angle of 180 degrees. So those would be the least similar, which also makes sense. In general, more similar vectors will have smaller angles between them. Does that help?\n",
    "\n",
    "Q2: Why does cosine similarity promote retrieving popular products?\n",
    "\n",
    "That is about why minimizing angle makes sense. The popularity thing is a separate question: why is minimizing angle better than minimizing distance? In the case of sparse high-dimensional vectors, you will often have very few nonzeros (i.e. reviewers) in common. Thus, the dot product between any two vectors will often be zero or very close to zero. If the dot product is zero, that means the angle is 90 degrees, which is the largest possible angle here because we don't have negative values. So, in short, most vectors are pointing in different directions from each other. Now, imagine trying to find the closest vector to a query vector. In that case, if you have another vector of the same length but pointing in a different direction, the distance will be greater than the distance between yourself and a tiny vector (near zero). So, the closest vectors by distance will often by vectors with extremely low length (number of reviews, aka popularity). OTOH the angle (cosine similarity) doesn't have this strange property and so tends to work better. Thus, I would say Euclidean distance tends to select very unpopular items rather than saying cosine similarity tends to select very popular items."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's use cosine similarity instead of Euclidean distance in `NearestNeighbors`, and find the 5 products most similar to `B00CFM0P7Y`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 114,
   "metadata": {},
   "outputs": [],
   "source": [
    "nn_cos = NearestNeighbors(n_neighbors=6, metric='cosine')\n",
    "nn_cos.fit(X_item_user);"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 115,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 93652, 103866, 103867, 103865,  98068,  98066]])"
      ]
     },
     "execution_count": 115,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "distances_cos, nearby_items_cos = nn_cos.kneighbors(X_item_user[grill_brush_ind])\n",
    "nearby_items_cos"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 116,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([103866, 103867, 103865,  98068,  98066])"
      ]
     },
     "execution_count": 116,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "nearby_items_cos = np.squeeze(nearby_items_cos)[1:]\n",
    "nearby_items_cos"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's compare the results of Euclidean vs. cosine distance:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 117,
   "metadata": {},
   "outputs": [],
   "source": [
    "def print_item_pop(items_idx):\n",
    "    popularity = np.sum(X_item_user[items_idx], axis=1)\n",
    "    for item in items_idx:\n",
    "        disp_url(item_inverse_mapper[item])\n",
    "    for i in range(len(items_idx)):\n",
    "        print(f\"Total stars: {int(popularity[i,0])}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 118,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Euclidean items/popularity:\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<a href=\"https://www.amazon.com/dp/B00IJB5MCS\">https://www.amazon.com/dp/B00IJB5MCS</a>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<a href=\"https://www.amazon.com/dp/B00IJB4MLA\">https://www.amazon.com/dp/B00IJB4MLA</a>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<a href=\"https://www.amazon.com/dp/B00EXE4O42\">https://www.amazon.com/dp/B00EXE4O42</a>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<a href=\"https://www.amazon.com/dp/B00743MZCM\">https://www.amazon.com/dp/B00743MZCM</a>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<a href=\"https://www.amazon.com/dp/B00HVXQY9A\">https://www.amazon.com/dp/B00HVXQY9A</a>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Total stars: 266\n",
      "Total stars: 205\n",
      "Total stars: 5\n",
      "Total stars: 5\n",
      "Total stars: 5\n"
     ]
    }
   ],
   "source": [
    "print(\"Euclidean items/popularity:\")\n",
    "print_item_pop(nearby_items)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 119,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Cosine items/popularity:\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<a href=\"https://www.amazon.com/dp/B00IJB5MCS\">https://www.amazon.com/dp/B00IJB5MCS</a>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<a href=\"https://www.amazon.com/dp/B00IJB8F3G\">https://www.amazon.com/dp/B00IJB8F3G</a>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<a href=\"https://www.amazon.com/dp/B00IJB4MLA\">https://www.amazon.com/dp/B00IJB4MLA</a>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<a href=\"https://www.amazon.com/dp/B00EF45AHU\">https://www.amazon.com/dp/B00EF45AHU</a>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<a href=\"https://www.amazon.com/dp/B00EF3YF0Y\">https://www.amazon.com/dp/B00EF3YF0Y</a>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Total stars: 266\n",
      "Total stars: 438\n",
      "Total stars: 205\n",
      "Total stars: 311\n",
      "Total stars: 513\n"
     ]
    }
   ],
   "source": [
    "print(\"Cosine items/popularity:\")\n",
    "print_item_pop(nearby_items_cos)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The results make sense, as we can expect more popular items given by the cosine metric than the Euclidean distance. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### (optional) Scaling\n",
    "\n",
    "BTW, should we scale our features?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "np.mean(X_item_user, axis=0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- Should we subtract these means?\n",
    "\n",
    "<br><br><br><br><br><br>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- Problem: if we do, it would no longer be mostly zeros!\n",
    "- If we had to store this as a dense matrix, it would be way too large.\n",
    "- Can we still scale if we can't shift? Sure, although there are some computational issues with sparse matrices.\n",
    "- It turns out scaled Euclidean and cosine similarity yield the name KNNs."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### (optional) Intuition in high dimensions\n",
    "\n",
    "- In the cities case, we had 2 dimensions.\n",
    "- In the Amazon case, we have"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "n_users"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- A lot of our intuition is not usable in high dimensions.\n",
    "- Example: you have a circle of radius 1. What fraction of the area is within radius 0.999?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "r = 0.999\n",
    "np.pi*r*r/(np.pi*1*1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Most of the area, as expected!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- Actually we don't care about the factors, which cancel out.\n",
    "- And the denominator is 1.\n",
    "- So the ratio is just $r^2$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "r**2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You have a sphere of radius 1. What fraction of the volume is within radius 0.999?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "r**3"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- Still most. \n",
    "- OK, what about 700,000 dimensions? Any guesses?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "r**n_users"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- Basically nothing. What about radius 0.999999?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "0.999999 ** n_users"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "All that is to say, don't trust your intuition when there's a huge number of dimensions!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Not covered / extensions\n",
    "\n",
    "- Can I recommend a project _for a specific user_?\n",
    "- One approach: Can we predict the missing ratings? (And then take high predicted rating.)\n",
    "- That was the Netflix Prize I mentioned earlier.\n",
    "- This is covered in CPSC 340 using a method called collaborative filtering."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "celltoolbar": "Slideshow",
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
